diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3b1be540f..289bca2c4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -28,7 +28,7 @@ intentionally not included. --> - Provider, bridge, target-format, or platform compatibility: - Evidence, artifact, provenance, or reconstruction contract: - Process execution, authorization, cleanup, or containment impact: -- Generated metadata (`docs/product-catalog.json`), package, or installation impact: +- Generated metadata (`docs/product-catalog.json`), package, or installation impact (the regen-generated bot pushes tracked generated files; never hand-edit them): ## Evidence and regression coverage diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 740e7f2a5..d317a4eb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: [main] pull_request: - branches: [main] + branches: [main, "release/*"] concurrency: group: ci-${{ github.event.pull_request.number || github.ref }} @@ -102,6 +102,8 @@ jobs: with: name: rea-dist path: dist + - name: Generate source catalog from downloaded build + run: node scripts/generate-mcp-tool-catalog.mjs - run: npm run test:ci:shard:run -- --shard=${{ matrix.shard }} - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} @@ -229,7 +231,16 @@ jobs: with: node-version-file: .nvmrc - name: Prepare cross compiler - run: sudo apt-get update && sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64 + run: | + # Slow Azure archive transfers can exhaust the job before compilation. + # Keep the same signed packages and avoid slow HTTP mirror fallback. + for source in /etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/ubuntu.sources; do + if [ -f "$source" ]; then + sudo sed -i 's|azure.archive.ubuntu.com|archive.ubuntu.com|g; s|http://archive.ubuntu.com|https://archive.ubuntu.com|g' "$source" + fi + done + sudo apt-get update + sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64 - run: npm run build:windows-native - run: npm run verify:windows-native:artifact - uses: actions/upload-artifact@v4 @@ -314,6 +325,8 @@ jobs: run: npm run test:focused -- tests/boundary/cli/nativePlistCli.test.ts tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts tests/boundary/cli/nativePlistSelectionFailureCli.test.ts - name: Native Swift demangling CLI and MCP regressions run: npm run test:focused -- tests/boundary/cli/nativeDemangleCli.test.ts tests/boundary/mcp/nativeDemangleMcp.test.ts + - name: Process filesystem snapshot descriptor regressions + run: npm run test:focused -- src/process/capture/FilesystemSnapshot.test.ts reconstruction-readiness: name: Reconstruction readiness @@ -433,7 +446,6 @@ jobs: tests/boundary/filesystem/asarInventoryFreshness.test.ts tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts tests/conformance/providers/ghidra/windowsCapabilities.test.ts - tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts src/ghidra/GhidraInstallation.test.ts tests/boundary/providers/ghidra/ghidraTransport.test.ts tests/boundary/providers/ghidra/ghidraTargetSnapshot.test.ts diff --git a/.github/workflows/real-ghidra-windows.yml b/.github/workflows/real-ghidra-windows.yml index 6bebfa623..42dc38fa4 100644 --- a/.github/workflows/real-ghidra-windows.yml +++ b/.github/workflows/real-ghidra-windows.yml @@ -21,7 +21,14 @@ jobs: with: node-version-file: .nvmrc - name: Prepare cross compiler - run: sudo apt-get update && sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64 + run: | + for source in /etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/ubuntu.sources; do + if [ -f "$source" ]; then + sudo sed -i 's|azure.archive.ubuntu.com|archive.ubuntu.com|g; s|http://archive.ubuntu.com|https://archive.ubuntu.com|g' "$source" + fi + done + sudo apt-get update + sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64 - run: npm run build:windows-native - run: npm run verify:windows-native:artifact - uses: actions/upload-artifact@v4 @@ -53,10 +60,18 @@ jobs: env: REA_ANALYSIS_PROVIDER: ghidra run: npm run verify:ghidra:windows | Tee-Object -FilePath windows-ghidra-proof.log + - name: Verify native x86 Windows Ghidra operations + env: + REA_ANALYSIS_PROVIDER: ghidra + run: npm run verify:ghidra:windows -- --x86 | Tee-Object -FilePath windows-ghidra-x86-proof.log - name: Verify ordinary-user CLI and MCP operations env: REA_ANALYSIS_PROVIDER: ghidra run: npm run verify:ghidra:windows:package | Tee-Object -FilePath windows-ghidra-package-proof.log + - name: Verify ordinary-user x86 CLI and MCP operations + env: + REA_ANALYSIS_PROVIDER: ghidra + run: npm run verify:ghidra:windows:package -- --x86 | Tee-Object -FilePath windows-ghidra-x86-package-proof.log - uses: actions/upload-artifact@v4 if: always() with: @@ -64,4 +79,6 @@ jobs: path: | windows-ghidra-proof.log windows-ghidra-package-proof.log + windows-ghidra-x86-proof.log + windows-ghidra-x86-package-proof.log retention-days: 30 diff --git a/.github/workflows/real-web-network-captures.yml b/.github/workflows/real-web-network-captures.yml new file mode 100644 index 000000000..91681043a --- /dev/null +++ b/.github/workflows/real-web-network-captures.yml @@ -0,0 +1,75 @@ +name: Real historical web network captures + +on: + workflow_dispatch: + pull_request: + paths: + - ".github/workflows/real-web-network-captures.yml" + - "bridge/mitmproxy/**" + - "src/browser/history/**" + - "src/domain/webNetworkCapture*.ts" + - "src/domain/analysisError*.ts" + - "src/contracts/errorSchemas.ts" + - "src/domain/explicitSensitive*.ts" + - "src/application/WebNetworkCapture*" + - "src/composition/webNetworkCaptures.ts" + - "src/cli/webNetworkCaptureCommands.ts" + - "src/server/registerWebNetworkCaptureTool.ts" + - "src/server/createServer.ts" + - "src/server/toolRegistrationOptions.ts" + - "src/cli.ts" + - "src/cliCommandNames.ts" + - "src/contracts/toolContracts.ts" + - "src/contracts/toolEffects.ts" + - "src/process/OwnedCommand.ts" + - "src/process/ProviderProcess.ts" + - "src/process/ProviderDeadline.ts" + - "src/process/ProcessOwnership*.ts" + - "src/process/PrivateRuntimeRoot.ts" + - "src/process/capture/ProcessCaptureLifecycle.ts" + - "src/artifacts/readStableArtifact*.ts" + - "src/artifacts/ArtifactReader.ts" + - "src/contracts/webNetworkCaptureToolContracts.ts" + - "scripts/verify-web-network-captures.mjs" + - "scripts/fixtures/generate-mitmproxy-capture.py" + - "package.json" + - "package-lock.json" + +permissions: + contents: read + +concurrency: + group: real-web-network-captures-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + HUSKY: "0" + NODE_OPTIONS: --max-old-space-size=768 + +jobs: + real-web-network-captures: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + - name: Use pinned package manager + run: npm install --global npm@11.16.0 + - run: npm ci + - name: Acquire fixed upstream native profile in runner temporary storage + shell: bash + run: | + capture_tool_root="${RUNNER_TEMP}/rea-mitmproxy-12.2.3" + mkdir -p "${capture_tool_root}" + curl --fail --location --retry 3 --max-time 180 \ + https://downloads.mitmproxy.org/12.2.3/mitmproxy-12.2.3-linux-x86_64.tar.gz \ + --output "${capture_tool_root}/mitmproxy.tar.gz" + echo '2e95286b618fa6fd33e5e62a78c2e5112571d85f42ec2bac29b97ee242bdb5c5 mitmproxy.tar.gz' \ + | (cd "${capture_tool_root}" && sha256sum --check --strict) + tar -xzf "${capture_tool_root}/mitmproxy.tar.gz" -C "${capture_tool_root}" + echo "REA_MITMDUMP_COMMAND=${capture_tool_root}/mitmdump" >> "${GITHUB_ENV}" + - name: Verify upstream producer fixtures through public CLI and MCP + run: npm run verify:web:network-captures diff --git a/.github/workflows/regen-generated.yml b/.github/workflows/regen-generated.yml new file mode 100644 index 000000000..0ce5e9c5f --- /dev/null +++ b/.github/workflows/regen-generated.yml @@ -0,0 +1,75 @@ +name: Regenerate generated metadata + +on: + pull_request: + branches: [main] + paths: + - "src/**" + - "skills/**/SKILL.md" + - "scripts/generate-*.mjs" + - "scripts/check-doc-facts.mjs" + - "scripts/lib/*.mjs" + - "scripts/verify/managed/**" + - "scripts/fixtures/managed/**" + - "package.json" + - "package-lock.json" + +concurrency: + group: regen-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + HUSKY: "0" + +permissions: + contents: write + +jobs: + regen: + name: Regenerate and push tracked generated files + # Same-repo PRs only: the default token cannot push to forks. + # The bot-actor guard stops looped runs triggered by our own push. + if: >- + github.actor != 'github-actions[bot]' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + - run: npm ci + - run: npm run docs:generate + - name: Commit regenerated files + shell: bash + env: + HEAD_REF: ${{ github.event.pull_request.head.ref }} + run: | + generated_paths=( + docs/product-catalog.json + docs/error-contract.schema.json + docs/verification/managed-conformance-manifest.json + docs/verification/managed-conformance-ledger.json + skills/reverse-engineer-anything/SKILL.md + src/generatedPackageMetadata.ts + ) + if git diff --quiet -- "${generated_paths[@]}"; then + echo "Generated metadata is already canonical." + exit 0 + fi + git diff --check + # Refuse to sweep unrelated working-tree changes into the bot commit. + unexpected="$(git diff --name-only | grep -vxF -f <(printf '%s\n' "${generated_paths[@]}") || true)" + if [[ -n "${unexpected}" ]]; then + echo "docs:generate left unexpected tracked changes:" >&2 + printf '%s\n' "${unexpected}" >&2 + exit 1 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -- "${generated_paths[@]}" + git commit -m "chore: regenerate generated metadata" + git push origin "HEAD:${HEAD_REF}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5995f4c7e..b1f0f0033 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,24 @@ name: Release on: - push: - branches: [main] + workflow_dispatch: + inputs: + release_branch: + description: Frozen release branch (release/VERSION) + required: true + type: string + phase: + description: Prepare the release PR, or publish its reviewed merge + required: true + default: prepare + type: choice + options: [prepare, publish] env: HUSKY: "0" concurrency: - group: release-${{ github.ref }} + group: release-${{ inputs.release_branch }} cancel-in-progress: false jobs: @@ -20,39 +30,111 @@ jobs: pull-requests: write outputs: release_created: ${{ steps.release.outputs.release_created }} - tag_name: ${{ steps.release.outputs.tag_name }} + sha: ${{ steps.release.outputs.sha }} steps: + - name: Validate release selection + shell: bash + env: + RELEASE_BRANCH: ${{ inputs.release_branch }} + RELEASE_PHASE: ${{ inputs.phase }} + run: | + if [[ "${RELEASE_PHASE}" != prepare && "${RELEASE_PHASE}" != publish ]]; then + echo "phase must be prepare or publish." >&2 + exit 1 + fi + if [[ "${RELEASE_PHASE}" == prepare && "${GITHUB_REF}" != "refs/heads/main" ]]; then + echo "Run prepare from main; release_branch selects the frozen source." >&2 + exit 1 + fi + if [[ "${RELEASE_PHASE}" == publish && "${GITHUB_REF}" != "refs/heads/${RELEASE_BRANCH}" ]]; then + echo "Run publish from the selected frozen release branch so npm provenance records its source." >&2 + exit 1 + fi + if [[ ! "${RELEASE_BRANCH}" =~ ^release/[^/]+$ ]]; then + echo "release_branch must select a release/ branch; main is not a release checkpoint." >&2 + exit 1 + fi + if ! git check-ref-format "refs/heads/${RELEASE_BRANCH}"; then + echo "Invalid release_branch: ${RELEASE_BRANCH}" >&2 + exit 1 + fi + + - name: Resolve release branch tip + id: branch + shell: bash + env: + GH_TOKEN: ${{ github.token }} + RELEASE_BRANCH: ${{ inputs.release_branch }} + run: | + sha="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/${RELEASE_BRANCH}" --jq '.object | select(.type == "commit") | .sha')" + if [[ ! "${sha}" =~ ^[0-9a-f]{40}$ ]]; then + echo "The release branch tip is not a commit." >&2 + exit 1 + fi + echo "sha=${sha}" >> "${GITHUB_OUTPUT}" + + - name: Require the publish dispatch to match the branch tip + if: inputs.phase == 'publish' + shell: bash + env: + BRANCH_SHA: ${{ steps.branch.outputs.sha }} + run: | + if [[ "${BRANCH_SHA}" != "${GITHUB_SHA}" ]]; then + echo "The release branch tip differs from the dispatched checkpoint; refusing to create a release for a moved source." >&2 + exit 1 + fi + - uses: googleapis/release-please-action@v4 id: release with: - # A dedicated credential lets release-PR updates trigger CI. GitHub - # requires manual approval for pull_request events from GITHUB_TOKEN. + # A dedicated credential starts PR CI automatically; the built-in + # token creates approval-required PR runs for maintainers to approve. token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} + target-branch: ${{ inputs.release_branch }} + skip-github-release: ${{ inputs.phase == 'prepare' }} + skip-github-pull-request: ${{ inputs.phase == 'publish' }} + + - name: Require a merged release PR for publication + if: inputs.phase == 'publish' && steps.release.outputs.release_created != 'true' + run: | + echo "No new release was created. Merge the reviewed release PR into its frozen branch before publishing. For a partial publication, re-run failed jobs in the original run." >&2 + exit 1 + + - name: Bind publication to the dispatched checkpoint + if: inputs.phase == 'publish' && steps.release.outputs.release_created == 'true' + shell: bash + env: + RELEASE_SHA: ${{ steps.release.outputs.sha }} + run: | + if [[ "${RELEASE_SHA}" != "${GITHUB_SHA}" ]]; then + echo "The tagged release SHA differs from the dispatched checkpoint; refusing to publish mismatched source provenance. The GitHub release already exists; do not dispatch publish again." >&2 + exit 1 + fi - name: Check out release pull request - if: steps.release.outputs.prs_created == 'true' + if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true' uses: actions/checkout@v6 with: ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }} persist-credentials: false - name: Set up Node.js for generated documentation - if: steps.release.outputs.prs_created == 'true' + if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true' uses: actions/setup-node@v6 with: node-version-file: .nvmrc package-manager-cache: false - name: Install dependencies - if: steps.release.outputs.prs_created == 'true' + if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true' run: npm ci - name: Regenerate release documentation - if: steps.release.outputs.prs_created == 'true' + if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true' run: npm run docs:generate - name: Commit canonical release catalog - if: steps.release.outputs.prs_created == 'true' + if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true' shell: bash env: # Use the same identity for the follow-up commit so it also triggers CI. @@ -92,13 +174,16 @@ jobs: publish: name: Publish to npm needs: release-please - if: needs.release-please.outputs.release_created == 'true' + if: inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true' runs-on: ubuntu-latest permissions: contents: read id-token: write steps: - uses: actions/checkout@v6 + with: + ref: ${{ needs.release-please.outputs.sha }} + persist-credentials: false - uses: actions/setup-node@v6 with: @@ -135,28 +220,32 @@ jobs: verification_root="$(mktemp -d)" trap 'rm -rf -- "${verification_root}"' EXIT cd "${verification_root}" - for attempt in $(seq 1 80); do + max_attempts=120 + sleep_seconds=15 + total_window_minutes=$((max_attempts * sleep_seconds / 60)) + for attempt in $(seq 1 "${max_attempts}"); do published_version="$(npm view --prefer-online "rea-agents@${version}" version --json 2>/dev/null || true)" if [ "${published_version}" = "\"${version}\"" ]; then - echo "rea-agents@${version} is visible on npm after attempt ${attempt}; verifying the published CLI." + echo "rea-agents@${version} is visible on npm after attempt ${attempt}/${max_attempts}; verifying the published CLI." npm exec --yes --package="rea-agents@${version}" -- rea --help npm exec --yes --package="rea-agents@${version}" -- rea --llms node "${GITHUB_WORKSPACE}/scripts/verify-published-package.mjs" "${version}" exit 0 else - echo "rea-agents@${version} is not visible on npm yet (attempt ${attempt}/80; up to 20 minutes)" >&2 + echo "rea-agents@${version} is not visible on npm yet (attempt ${attempt}/${max_attempts}; up to ${total_window_minutes} minutes)" >&2 fi - if [ "${attempt}" -lt 80 ]; then - sleep 15 + if [ "${attempt}" -lt "${max_attempts}" ]; then + sleep "${sleep_seconds}" fi done - echo "rea-agents@${version} did not become visible on npm within 20 minutes" >&2 + echo "rea-agents@${version} did not become visible on npm within ${total_window_minutes} minutes (${max_attempts} attempts × ${sleep_seconds}s)" >&2 + echo "This indicates npm registry propagation delay, not a CLI verification failure." >&2 exit 1 publish-mcp: name: Publish to MCP Registry needs: [release-please, publish] - if: needs.release-please.outputs.release_created == 'true' && needs.publish.result == 'success' + if: inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true' && needs.publish.result == 'success' runs-on: ubuntu-latest permissions: contents: read @@ -164,7 +253,7 @@ jobs: steps: - uses: actions/checkout@v6 with: - ref: ${{ needs.release-please.outputs.tag_name }} + ref: ${{ needs.release-please.outputs.sha }} persist-credentials: false - name: Verify package and registry metadata versions diff --git a/.github/workflows/website-check.yml b/.github/workflows/website-check.yml index 4f410ef62..2fc7a6874 100644 --- a/.github/workflows/website-check.yml +++ b/.github/workflows/website-check.yml @@ -2,7 +2,7 @@ name: Website checks on: pull_request: - branches: [main] + branches: [main, "release/*"] paths: - "website/**" - "scripts/verify-website.py" diff --git a/.gitignore b/.gitignore index 330e03a16..aa778dec1 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ Thumbs.db # Generated website download /website/public/examples/notes-example.zip + +# Build-generated MCP tool catalog (regenerated by `npm run build`; never commit) +src/generatedMcpToolCatalog.ts diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 411256bca..32dbe0e2b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.1.0" + ".": "5.0.0" } diff --git a/AGENTS.md b/AGENTS.md index a3f3e35e4..c12152690 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Product Direction -REA exposes reverse-engineering tools through a CLI and MCP server. Hopper, the bring-your-own Ghidra adapter, and the bring-your-own IDA MCP adapter are operation-capable deep binary-analysis providers. Ghidra is supported on Linux x64 and macOS x64/arm64 with matching native decompiler tools, and has an experimental Windows x64 P0 boundary for approved native x86-64 PE applications; on Linux and macOS it supplies inventory, function analysis, and atomic function annotation edits in an ephemeral database, without modifying executable bytes or controlling a GUI. Windows P0 has no mutation authority. IDA adapts upstream legacy attached GUI and modern headless database-supervisor profiles for live read-only analysis; initial real verification covers Windows, and an attached GUI database is never saved or closed. Keep provider-specific code out of the domain and application layers. +REA exposes reverse-engineering tools through a CLI and MCP server. Hopper, the bring-your-own Ghidra adapter, and the bring-your-own IDA MCP adapter are operation-capable deep binary-analysis providers. Ghidra is supported on Linux x64 and macOS x64/arm64 with matching native decompiler tools, and has an experimental Windows x64 P0 boundary for approved native x86 and x86-64 PE applications; on Linux and macOS it supplies inventory, function analysis, and atomic function annotation edits in an ephemeral database, without modifying executable bytes or controlling a GUI. Windows P0 has no mutation authority. IDA adapts upstream legacy attached GUI and modern headless database-supervisor profiles for live read-only analysis; initial real verification covers Windows, and an attached GUI database is never saved or closed. Keep provider-specific code out of the domain and application layers. Prioritize: @@ -25,6 +25,7 @@ REA is a layered ESM TypeScript application. Dependencies flow inward from pure - `src/process/` owns shared process lifecycle primitives, not provider wire protocols. `bridge/` contains provider-side adapters. - `tests/` contains unit, composition, boundary, acceptance, and conformance tests. `scripts/verify-*` and capability directories under `scripts/verify/` contain real-toolchain checks. - `docs/product-catalog.json` is generated. Update its source contracts and regenerate it; do not edit it directly. +- `src/generatedMcpToolCatalog.ts` is build-generated and gitignored. Never commit it; resolve any trace of it in merges by deleting it and running `npm run build:cached`. ## Build, Test, and Development Commands diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc158cb7..c689b5c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,264 @@ # Changelog +## [5.0.0](https://github.com/morluto/rea/compare/rea-agents-4.1.0...rea-agents-5.0.0) (2026-10-07) + + +### ⚠ BREAKING CHANGES + +* **native:** preserve incomplete native UI captures ([#991](https://github.com/morluto/rea/issues/991)) +* **android:** Android analysis now requires a full JDK 17 or newer with its compiler module for the packaged Java source bridge. + +### Features + +* **apple:** project macOS bundle anatomy in application graphs ([88c940b](https://github.com/morluto/rea/commit/88c940b165d788a491e212ea93e00462c4dde6a2)) +* **apple:** project macOS bundle anatomy in application graphs ([dd3e7ae](https://github.com/morluto/rea/commit/dd3e7aef0d52c478f6cdf475501c42ebe7f5c97e)) +* **application:** wire Android and Apple inventory graph tools ([#852](https://github.com/morluto/rea/issues/852)) ([e0fd820](https://github.com/morluto/rea/commit/e0fd8207a1fb738bcc34f204b55c4fa154f5f02d)) +* **browser:** attribute native execution and listener sources ([#917](https://github.com/morluto/rea/issues/917)) ([b5afa5f](https://github.com/morluto/rea/commit/b5afa5f91eed9ae31d670aeab9bbf5112d790ec2)) +* **browser:** capture correlated network content and lifecycle evidence ([#797](https://github.com/morluto/rea/issues/797)) ([f9eaf05](https://github.com/morluto/rea/commit/f9eaf056ef58215094513176a70fabae0740081f)) +* **browser:** trace captured website module imports ([#879](https://github.com/morluto/rea/issues/879)) ([01be082](https://github.com/morluto/rea/commit/01be082e9e11c78b91828c06a9c543c754783c07)) +* **browser:** trace captured website source-map locations ([#897](https://github.com/morluto/rea/issues/897)) ([f430c41](https://github.com/morluto/rea/commit/f430c416c72d444fbe521e0263f8935da4cdb040)) +* **javascript:** recover readable modules with Wakaru ([#848](https://github.com/morluto/rea/issues/848)) ([166dcd2](https://github.com/morluto/rea/commit/166dcd21d06f687a9c480a6a6d36beb3b4954dd0)) +* **native:** decode chained fixups, binds, ObjC categories and properties ([a657557](https://github.com/morluto/rea/commit/a65755728f7a10d984514c75fa863b84ddcdaee9)) +* **native:** decode chained fixups, binds, ObjC categories and properties ([3e24a72](https://github.com/morluto/rea/commit/3e24a72465fe6c628db1f836e93d74d7f4a92b80)) +* **native:** open iOS-style and iOS-on-Mac wrapper app bundles ([2ed89e6](https://github.com/morluto/rea/commit/2ed89e6529924248d2e21084f8730bedbb16bc3f)) +* **native:** open iOS-style and iOS-on-Mac wrapper app bundles ([aeebba3](https://github.com/morluto/rea/commit/aeebba391ee5c288b75cd828e7d18d32b40aa698)) +* **web:** export captured scripts for JavaScript application analysis ([#801](https://github.com/morluto/rea/issues/801)) ([472b72a](https://github.com/morluto/rea/commit/472b72a53068a8e2ec78fe239d687023482daaa7)) +* **website:** add a shared back-to-top link ([5047373](https://github.com/morluto/rea/commit/504737300632d8644fbd6c658d2c97e38bb54c3b)) +* **website:** introduce REA and the DX-Ball reconstruction ([#906](https://github.com/morluto/rea/issues/906)) ([4e3953c](https://github.com/morluto/rea/commit/4e3953cc45ce9c21786a7a6fc495d2350aa85d24)) + + +### Bug Fixes + +* address review nits from batch integration follow-ups ([#818](https://github.com/morluto/rea/issues/818)) ([5a0e0ba](https://github.com/morluto/rea/commit/5a0e0ba3b81ea0ec336addb07da98ec7bc0a35d3)) +* **android:** align runtime suffix inference ([#977](https://github.com/morluto/rea/issues/977)) ([fb3749a](https://github.com/morluto/rea/commit/fb3749aff692a7a8225dff00a4516b48a76866b6)) +* **android:** bound fragmented JADX MCP frame memory ([#937](https://github.com/morluto/rea/issues/937)) ([9e39795](https://github.com/morluto/rea/commit/9e397958951edb543134e3ad57e7ab253d6df21a)) +* **android:** preserve JVM settings and reuse metadata sessions ([#908](https://github.com/morluto/rea/issues/908)) ([eab34a9](https://github.com/morluto/rea/commit/eab34a9d8c5eebad6b04b85750be0276d679c3f9)) +* **apple:** attribute only application-root components in macOS inventories ([5524970](https://github.com/morluto/rea/commit/55249704c5c83cbf8d20ac7869863cc196da00a6)) +* **apple:** count only real framework version directories ([2e81e86](https://github.com/morluto/rea/commit/2e81e867b24da8314f666f62d154de5803040cdf)) +* **apple:** pair bridge hypotheses within one application root ([7acf4e7](https://github.com/morluto/rea/commit/7acf4e741a6a314ea7aabb18fc757e196158ff76)) +* **apple:** pair bridge hypotheses within one application root ([9226ade](https://github.com/morluto/rea/commit/9226ade5114f287eb8e1b00b08dd91da1cc66003)) +* **apple:** traverse keyed archive hierarchy collections ([#976](https://github.com/morluto/rea/issues/976)) ([71ac83f](https://github.com/morluto/rea/commit/71ac83f3dd376135098f460c86e7897183456ec5)) +* **artifacts:** accept APFS image disks ejected with their container ([d93ebec](https://github.com/morluto/rea/commit/d93ebec9df951d40e66ced667f07fadd2dd5d25a)) +* **artifacts:** accept APFS image disks ejected with their container ([b72c079](https://github.com/morluto/rea/commit/b72c079a9a90c374bd6dd30ff8c4387306b8f59b)) +* **artifacts:** accept the bundle subject when extracting an app bundle ([#891](https://github.com/morluto/rea/issues/891)) ([37d2eba](https://github.com/morluto/rea/commit/37d2ebac4c2a524eec6e32c4c57cbbb632491ad8)) +* **artifacts:** decode flat keyed-archive NIBs with data and shared references ([b3f013f](https://github.com/morluto/rea/commit/b3f013fa183e8351319a053575aeee5df337c276)) +* **artifacts:** decode flat keyed-archive NIBs with data and shared references ([2a4667b](https://github.com/morluto/rea/commit/2a4667bb79117b202abb009c0bb9ebfdfde57158)) +* **artifacts:** harden reader lifecycle boundaries ([#951](https://github.com/morluto/rea/issues/951)) ([2366bda](https://github.com/morluto/rea/commit/2366bda37b1084016d42a0d06920dc57e7c7723f)) +* **artifacts:** keep the post-snapshot detach check out of provenance ([62634e4](https://github.com/morluto/rea/commit/62634e4c0177bde8a08decc356da98fbe25d2000)) +* **artifacts:** preserve reader failure diagnostics ([#987](https://github.com/morluto/rea/issues/987)) ([fc4a9a0](https://github.com/morluto/rea/commit/fc4a9a05922ce8474fd0717bc800343b0a95bd41)) +* **artifacts:** project compiled AppKit actions from the control to its target ([b08228d](https://github.com/morluto/rea/commit/b08228d19f8a0f6dbc940e8d322ef2bb42d2ce1a)) +* **artifacts:** project compiled AppKit actions from the control to its target ([#905](https://github.com/morluto/rea/issues/905)) ([900d7c8](https://github.com/morluto/rea/commit/900d7c8606cbc1ccbca818827a55bf2eab28bca4)) +* **artifacts:** project UIKit runtime outlet and event connections ([aa8082e](https://github.com/morluto/rea/commit/aa8082ea6c0898feb4d842135aea6b9539f769f8)) +* **artifacts:** project UIKit runtime outlet and event connections ([8b24387](https://github.com/morluto/rea/commit/8b243877c22a5550d005e7b8abf93791ce8566b9)) +* **artifacts:** read AppKit connectors from flat keyed-archive NIBs ([#900](https://github.com/morluto/rea/issues/900)) ([27c272b](https://github.com/morluto/rea/commit/27c272bd9a42e7235c7991881196c60dbc126714)) +* **artifacts:** read keyed archive dictionaries keyed UID as dictionaries ([cfd2b0c](https://github.com/morluto/rea/commit/cfd2b0c75dfb9d51b936788a46d0338145803ece)) +* **artifacts:** read keyed archive dictionaries keyed UID as dictionaries ([35c82b9](https://github.com/morluto/rea/commit/35c82b93eb2579687bb446c3a8a2a9f1a7563d28)) +* **artifacts:** report which path constraint failed ([#847](https://github.com/morluto/rea/issues/847)) ([3f1a77f](https://github.com/morluto/rea/commit/3f1a77f68ec4355d8b792ccb2970bc03485889c9)) +* **browser:** distinguish computed call keys from identifiers ([#808](https://github.com/morluto/rea/issues/808)) ([02beb74](https://github.com/morluto/rea/commit/02beb745358f02bdaabed4bb523c5e3527a9841e)) +* **browser:** distinguish WebMCP registration frame owners ([#813](https://github.com/morluto/rea/issues/813)) ([b3b44d8](https://github.com/morluto/rea/commit/b3b44d8a2d8b2aa70a8ed8b9cddf365865fcc53f)) +* **browser:** fingerprint cookies at the selected page URL ([#763](https://github.com/morluto/rea/issues/763)) ([78014e3](https://github.com/morluto/rea/commit/78014e3d149f762c848581a0aac7e44f75d51225)) +* **browser:** identify NodeNext TypeScript original source artifacts ([#811](https://github.com/morluto/rea/issues/811)) ([e84ad86](https://github.com/morluto/rea/commit/e84ad86a7578fb46568663aca6fe6a6f3986cc8d)) +* **browser:** preserve evidence through deep AST analysis ([#924](https://github.com/morluto/rea/issues/924)) ([f826aa8](https://github.com/morluto/rea/commit/f826aa893fa7d55e87d907f0c94c39421a166dd8)) +* **browser:** preserve form destinations and satisfies mutation uncertainty ([#825](https://github.com/morluto/rea/issues/825)) ([6dd719e](https://github.com/morluto/rea/commit/6dd719eaf605b37cd5dad6d01d33527ca9d59f06)), closes [#820](https://github.com/morluto/rea/issues/820) +* **browser:** preserve Link header value delimiters ([#805](https://github.com/morluto/rea/issues/805)) ([dd36405](https://github.com/morluto/rea/commit/dd3640548fe3d1e8b92784a63fac55d757dcc1f0)) +* **build:** validate installed dependencies before cache hits ([#982](https://github.com/morluto/rea/issues/982)) ([a45fe86](https://github.com/morluto/rea/commit/a45fe86a39b9e7a7c21e3665b6de4763cb72d782)) +* capture IndexedDB object-store record fingerprints ([#809](https://github.com/morluto/rea/issues/809)) ([21a8bd0](https://github.com/morluto/rea/commit/21a8bd0ccde2326c8bc022714a88a438d8b89dde)) +* **cli:** align structured output guard with native flags ([#777](https://github.com/morluto/rea/issues/777)) ([e9fac56](https://github.com/morluto/rea/commit/e9fac56232e74c8287863ca5c4cc7d8e39fb4a40)) +* **cli:** emit empty structured filter projections ([#914](https://github.com/morluto/rea/issues/914)) ([2db668f](https://github.com/morluto/rea/commit/2db668f6ca91d2514a354fd459239b84031812e7)) +* **cli:** honor the selected native plist path ([#773](https://github.com/morluto/rea/issues/773)) ([0374865](https://github.com/morluto/rea/commit/0374865836aa4960657fb92f588c71e35dd3bed8)) +* **cli:** preserve output and comparison input validation ([#981](https://github.com/morluto/rea/issues/981)) ([d5c43d6](https://github.com/morluto/rea/commit/d5c43d65f0f6a1de8fb2eb5f2c1940a6ea2af20c)) +* **cli:** preserve unsupported historical-source imports ([#795](https://github.com/morluto/rea/issues/795)) ([911d580](https://github.com/morluto/rea/commit/911d58063477912042da5e9e573a9e8008956622)) +* **contracts:** align CLI/MCP boundary contracts ([#946](https://github.com/morluto/rea/issues/946)) ([4a60738](https://github.com/morluto/rea/commit/4a6073810a955d058c10f209157cbd68832a92c5)) +* **deps:** remediate smol-toml advisories ([93091ab](https://github.com/morluto/rea/commit/93091abb4dbd3cceee217d179db816895df24d70)) +* **deps:** remediate smol-toml advisories ([c55ae3b](https://github.com/morluto/rea/commit/c55ae3b9eccc17e6e8f4369e13044995d0650704)) +* **dotnet:** compare members with undecoded signatures by exact identity ([28fd49f](https://github.com/morluto/rea/commit/28fd49f0f6cf54e373411f5999e6b5722dd826f5)) +* **dotnet:** compare members with undecoded signatures by exact identity ([d52af3b](https://github.com/morluto/rea/commit/d52af3b321a321964598fb302edeb26552c9dffd)) +* **dotnet:** inspect native boundaries of non-managed and malformed PEs ([d5f28fb](https://github.com/morluto/rea/commit/d5f28fb29c5c3e8acef940fcae042e660e3a4d72)) +* **dotnet:** inspect native boundaries of non-managed and malformed PEs ([d98dcfa](https://github.com/morluto/rea/commit/d98dcfa8c2e46a7cc918aeb01058c26c4a84060c)) +* **dotnet:** keep a decoded member unknown beside an undecoded counterpart ([76b7020](https://github.com/morluto/rea/commit/76b70203d941ab85c1772f330407fd832d438c9c)) +* **dotnet:** keep admitted CLI header facts when metadata is unreadable ([e5ba693](https://github.com/morluto/rea/commit/e5ba6934e4ff819c09077ca70bd2417d37f85bc0)) +* **dotnet:** name the shared key in exact-signature ambiguities ([7714e76](https://github.com/morluto/rea/commit/7714e763d9ac2e87994068b3dee37ca6c5ffc6f4)) +* **dotnet:** treat ambiguous undecoded members as possible counterparts ([73370e1](https://github.com/morluto/rea/commit/73370e1c064899753988581c9a821e0e7f579561)) +* **errors:** report union input issues at the union's path ([bddeb63](https://github.com/morluto/rea/commit/bddeb634b583cf4f7d5fa6cf3a1ee821e05d2923)) +* **evaluation:** canonicalize Unicode argument keys ([#980](https://github.com/morluto/rea/issues/980)) ([0a4caf4](https://github.com/morluto/rea/commit/0a4caf4a0a1355b6a121fc88886b22e55843ac76)) +* **firmware:** accept the verified Binwalk and Unblob release lines ([#844](https://github.com/morluto/rea/issues/844)) ([bef4321](https://github.com/morluto/rea/commit/bef4321afb3d0d480e8a04ad7e256663542caa3d)) +* **firmware:** retain multi-file extraction failures ([#978](https://github.com/morluto/rea/issues/978)) ([8aaf9a1](https://github.com/morluto/rea/commit/8aaf9a19788beb6612332505850ad3407cb00027)) +* **ghidra:** admit the 12.1 line and the install's JDK range ([#840](https://github.com/morluto/rea/issues/840)) ([5cbf06c](https://github.com/morluto/rea/commit/5cbf06c7ad0ace599721bffd7da566b8a790b622)) +* **ghidra:** select exact packaged bridge scripts ([#800](https://github.com/morluto/rea/issues/800)) ([894c30e](https://github.com/morluto/rea/commit/894c30ef02d946f0c8de3ae6c9168d9525d95033)) +* **ida:** share concurrent session cleanup ([#950](https://github.com/morluto/rea/issues/950)) ([f14b6a8](https://github.com/morluto/rea/commit/f14b6a84306116716e78c5ffc84353ac07939593)) +* **installer:** validate exact semantic versions ([#954](https://github.com/morluto/rea/issues/954)) ([248972f](https://github.com/morluto/rea/commit/248972f327776687037dc07099117584c0674e77)) +* integrate 24 reviewed browser/native/semantics corrections ([#815](https://github.com/morluto/rea/issues/815)) ([3af3811](https://github.com/morluto/rea/commit/3af3811ab0a212b6aab6185628d47ecad37958fc)) +* **javascript:** advertise the exact empty seed rule and match empty API keys ([35e5508](https://github.com/morluto/rea/commit/35e5508fd095a062d7477bd6bb7d2d8e28201cd8)) +* **javascript:** analyze source maps with empty source names ([58ef8ac](https://github.com/morluto/rea/commit/58ef8ac3aa803d7b64dd12a5b2a2e8ec24261060)) +* **javascript:** analyze sources containing empty string literals ([e7cbfe0](https://github.com/morluto/rea/commit/e7cbfe04fdfd7affe50dba9e2c7a9a0a72291041)) +* **javascript:** analyze sources containing empty string literals ([11fc3b9](https://github.com/morluto/rea/commit/11fc3b981509e8107b10b2e75363f5bb489879db)) +* **javascript:** filter keyed-collection receivers only for get/delete ([00f707b](https://github.com/morluto/rea/commit/00f707b250451246d69a1205c39c6843fab6bb5b)) +* **javascript:** handle deep AST traversal and addition without stack overflow ([#915](https://github.com/morluto/rea/issues/915)) ([62b3711](https://github.com/morluto/rea/commit/62b37111e7f09887eb712c6e8e721e0334cf10b9)) +* **javascript:** handle deep static member paths ([#939](https://github.com/morluto/rea/issues/939)) ([348c65c](https://github.com/morluto/rea/commit/348c65c7ece1aee280f9431d995996b444e7549d)) +* **javascript:** keep bare relative URLs for computed-method XHR ([e32028d](https://github.com/morluto/rea/commit/e32028d1b566872ba8cd08fc6800c198d6aef30b)) +* **javascript:** keep computed-method XHR request URLs ([602d002](https://github.com/morluto/rea/commit/602d0021d45a6cdab9f806554f3ee12ea670ec97)) +* **javascript:** keep exact empty env keys and method names ([522c2d0](https://github.com/morluto/rea/commit/522c2d0ecb1eb7be262eaef2926cceac9a68badc)) +* **javascript:** keep exact empty env keys and method names ([f7954a8](https://github.com/morluto/rea/commit/f7954a8fd692f262ef3ce1507b957681150ce9c4)), closes [#783](https://github.com/morluto/rea/issues/783) +* **javascript:** keep exact empty require paths and request fields ([ad1f6b5](https://github.com/morluto/rea/commit/ad1f6b508fbdd44d8bf1884ed96ae69082808e7d)), closes [#783](https://github.com/morluto/rea/issues/783) +* **javascript:** keep exact empty values in graph identities ([f608127](https://github.com/morluto/rea/commit/f608127635ed8e0d105ad95252e5ea0dff899f5b)) +* **javascript:** keep exact empty values in graph identities ([63012ee](https://github.com/morluto/rea/commit/63012eeb601fc74ca82f3a027d852a5da1b099d2)) +* **javascript:** keep keyed-collection reads out of endpoints ([9075ce7](https://github.com/morluto/rea/commit/9075ce71ada4334407aa2d194dc82e3031e30260)) +* **javascript:** keep keyed-collection reads out of endpoints ([eb68761](https://github.com/morluto/rea/commit/eb68761eb0a35b876e33dd6115d0d07f97e99dd5)) +* **javascript:** keep literal HTTP methods on storage-named receivers ([9f48a76](https://github.com/morluto/rea/commit/9f48a76e1186cc43c6928eff1c82e93280caee1a)) +* **javascript:** keep literal-method and URL-literal requests on aliased receivers ([e3e7664](https://github.com/morluto/rea/commit/e3e76646657e950e24241ba715eb97ea385933e9)) +* **javascript:** keep mutated object initializer values unknown ([#752](https://github.com/morluto/rea/issues/752)) ([6caa38a](https://github.com/morluto/rea/commit/6caa38a0cad369ddff52b08f8d7d44c1b92ecac0)) +* **javascript:** keep storage open versions out of XHR endpoints ([4f51103](https://github.com/morluto/rea/commit/4f5110306d9edab94bf63bf81cf259324d96af57)) +* **javascript:** keep storage open versions out of XHR endpoints ([898b0f6](https://github.com/morluto/rea/commit/898b0f62d76ea1cf793140cb255cad16ab85b44d)) +* **javascript:** keep XHR and client endpoints that receiver spellings hid ([05f43ed](https://github.com/morluto/rea/commit/05f43ed388ae4e9316d320b592b6eba5b119ce89)) +* **javascript:** leave empty application values unlabeled ([527b76e](https://github.com/morluto/rea/commit/527b76ef78e84d29bb41bd45f1f83fae05ca66ca)) +* **javascript:** match exact empty literal channels and seeds ([e20da1f](https://github.com/morluto/rea/commit/e20da1f205b105b1b6a5decd3c2d0fe36fd12410)) +* **javascript:** never read window or document open as XHR ([ae849a7](https://github.com/morluto/rea/commit/ae849a72811857ba9755962ec48a1d76c9d1181a)) +* **javascript:** preserve computed Electron member uncertainty ([3a4a63f](https://github.com/morluto/rea/commit/3a4a63f70a2e42fb1fa48881e08b08d612289f2e)) +* **javascript:** preserve computed Electron member uncertainty ([1260cac](https://github.com/morluto/rea/commit/1260cacc67b8e0c93fb9434452053feca956e7e7)) +* **javascript:** project decoded file URL paths ([#810](https://github.com/morluto/rea/issues/810)) ([2572e1e](https://github.com/morluto/rea/commit/2572e1eb2f853a4cebfc8303d9e293e1d106d608)) +* **javascript:** read open() endpoints only from XHR method/URL pairs ([63e0822](https://github.com/morluto/rea/commit/63e0822cc794bd6ec1688340b3230ffb6b5c4a8c)) +* **javascript:** read open() endpoints only from XHR method/URL pairs ([1be3705](https://github.com/morluto/rea/commit/1be370543f7ea39016f86ebdec80a8ceaf7f8a28)) +* **javascript:** read substitution-free templates as exact strings ([c371d6e](https://github.com/morluto/rea/commit/c371d6e1e9cceb28e6cfc874e14c4e740bb87751)) +* **javascript:** read substitution-free templates as exact strings ([77cf757](https://github.com/morluto/rea/commit/77cf75710369acc6c304ec352ab25aa7274e04b1)) +* **javascript:** resolve bare source-map and worker URLs next to the script ([#890](https://github.com/morluto/rea/issues/890)) ([45fcd28](https://github.com/morluto/rea/commit/45fcd28b4018723bdb508a9229b74ab9eb749f75)) +* **javascript:** retain recovered call argument positions ([#929](https://github.com/morluto/rea/issues/929)) ([e6ba9c3](https://github.com/morluto/rea/commit/e6ba9c3af3053485f154825eea6a156127328be9)) +* **linux:** improve Hopper reliability and CachyOS support ([#963](https://github.com/morluto/rea/issues/963)) ([98b2b02](https://github.com/morluto/rea/commit/98b2b027afd8c80b68e9daaf97bdaa4d942105cc)) +* **mcp:** advertise reserved process environment key ([#949](https://github.com/morluto/rea/issues/949)) ([e0d76ae](https://github.com/morluto/rea/commit/e0d76ae80ddc70ae4d5afcbd1427afb81eee3669)) +* **mcp:** isolate optional observation adapter failures ([#838](https://github.com/morluto/rea/issues/838)) ([be3cfbf](https://github.com/morluto/rea/commit/be3cfbffdfbbb65da75dcec0518e2ebf387e66bc)) +* **mcp:** preserve exact guided prompt context ([#761](https://github.com/morluto/rea/issues/761)) ([66c26f2](https://github.com/morluto/rea/commit/66c26f280b1d494806ee182b52564166760f0ae2)) +* **native-ui:** handle incomplete accessibility child collections ([#944](https://github.com/morluto/rea/issues/944)) ([ac3bae3](https://github.com/morluto/rea/commit/ac3bae3cb97c4fb622eb1b6ac1f3b84e6a70ba03)) +* **native:** inspect plists containing data, dates, and non-finite reals ([bc2cd8b](https://github.com/morluto/rea/commit/bc2cd8b874e115eee446860043758a80bd583ad0)) +* **native:** inspect plists containing data, dates, and non-finite reals ([5ead2cf](https://github.com/morluto/rea/commit/5ead2cfbc47896b43502363d3568330f5d94762e)) +* **native:** keep echoed codesign paths out of signature fields ([#865](https://github.com/morluto/rea/issues/865)) ([4aa2b60](https://github.com/morluto/rea/commit/4aa2b60c1d84607a138395ae93260b2e50c2a721)) +* **native:** keep echoed Mach-O paths out of load commands and symbols ([ab985c6](https://github.com/morluto/rea/commit/ab985c6a10e9da856ed151100cee8459793c3573)) +* **native:** keep echoed Mach-O paths out of load commands and symbols ([37fe105](https://github.com/morluto/rea/commit/37fe105ba168488d05226f2912bed802e2d48d75)) +* **native:** keep integral plist reals beyond the JSON range as reals ([#860](https://github.com/morluto/rea/issues/860)) ([3b92a2b](https://github.com/morluto/rea/commit/3b92a2bceddaa1659654da08160813a3affbc6b7)) +* **native:** keep plist XML element types for large numbers ([f85056d](https://github.com/morluto/rea/commit/f85056d83e42a461c8c947792774580294d0ceb9)) +* **native:** keep the resolved bundle plist and permission failures ([418a556](https://github.com/morluto/rea/commit/418a5562f6f640503c751924a4787586c2c5c98a)) +* **native:** pass Swift symbols to swift-demangle as operands ([dd90965](https://github.com/morluto/rea/commit/dd90965058a9e6f7dd7f30eb7e462572e777db7f)) +* **native:** pass Swift symbols to swift-demangle as operands ([6c88094](https://github.com/morluto/rea/commit/6c88094da45258b346fe594764e561f999622a58)) +* **native:** preserve incomplete native UI captures ([#991](https://github.com/morluto/rea/issues/991)) ([9f496ef](https://github.com/morluto/rea/commit/9f496ef2d4a3aa68816287bf5d2ba1620d72c97a)) +* **native:** report exact plist integers beyond the JSON number range ([c9f49db](https://github.com/morluto/rea/commit/c9f49db907db0513ad8b22c9b2bbdcee400aff11)) +* **native:** report exact plist integers beyond the JSON number range ([430c297](https://github.com/morluto/rea/commit/430c297ca4ddb3491c2b835b889486e6a2ffdc17)) +* **native:** report plist permission denials as access failures ([5738a0c](https://github.com/morluto/rea/commit/5738a0cf8c111833847fecb2f7d0c2ee35eabbd6)) +* **native:** report why a selected plist cannot be inspected ([14f9b3d](https://github.com/morluto/rea/commit/14f9b3d93119a60a3cf5f3d9c138ca829a5462e8)) +* **native:** report why a selected plist cannot be inspected ([0674fac](https://github.com/morluto/rea/commit/0674facd468c3321584ece908031feee7220b9c6)) +* **plist:** keep character references beyond Unicode literal ([5afd8a8](https://github.com/morluto/rea/commit/5afd8a850b2ebd62e3400a5e2ece1a1cfa98bab6)) +* **plist:** read XML plists containing a __proto__ key ([faaee69](https://github.com/morluto/rea/commit/faaee693d5bb4bb957387ebd0f4338513c4c914a)) +* **plist:** read XML plists containing a __proto__ key ([a08be51](https://github.com/morluto/rea/commit/a08be51e9f0d23bd46b66bbdb88dbda44bc54c88)) +* **plist:** report omitted __proto__ entries in signatures and Interface Builder ([15ab5db](https://github.com/morluto/rea/commit/15ab5dbcaf3fac09756e98926373965247dad622)) +* **plist:** report omitted __proto__ entries in signatures and Interface Builder ([6729ee7](https://github.com/morluto/rea/commit/6729ee7636889a0a2ebc9fca8e35860476f8ca34)) +* preserve native UI failure reasons and dedupe version literals ([#854](https://github.com/morluto/rea/issues/854)) ([2d4a910](https://github.com/morluto/rea/commit/2d4a910fcd30e8d5b96fcb957b931e9aa3e4ab0c)) +* **process:** clarify Windows capture support and recovery ([#798](https://github.com/morluto/rea/issues/798)) ([50787b2](https://github.com/morluto/rea/commit/50787b2faf1a4bea799d041960ca2b91199ef403)) +* **process:** harden filesystem snapshot identity and cancellation ([#956](https://github.com/morluto/rea/issues/956)) ([93aa9eb](https://github.com/morluto/rea/commit/93aa9eb5c2551984182a4025d836e4223e8ecc8a)) +* **process:** retain provider output until streams close ([#928](https://github.com/morluto/rea/issues/928)) ([b929251](https://github.com/morluto/rea/commit/b9292516f4eedc2dedba713abf57f665ef1fb596)) +* **reference:** retain partial imports for unreadable directories ([#766](https://github.com/morluto/rea/issues/766)) ([cffc548](https://github.com/morluto/rea/commit/cffc548cd3127c5db811f20db4816039846b3170)) +* **release:** drive checkpoint releases from frozen branches ([fc58fa1](https://github.com/morluto/rea/commit/fc58fa1eb01fe5a8200a78e9551799c9c1a3d256)) +* **release:** publish from explicit source checkpoints ([49887f0](https://github.com/morluto/rea/commit/49887f0239da4483153a5e1610058619855525db)) +* **semantics:** isolate named function expression bindings ([#776](https://github.com/morluto/rea/issues/776)) ([ae81f64](https://github.com/morluto/rea/commit/ae81f64d2ad10e5cdbf8356819351c02480aee7a)) +* **semantics:** preserve direct promise ownership boundaries ([#758](https://github.com/morluto/rea/issues/758)) ([e1ca412](https://github.com/morluto/rea/commit/e1ca412757bb01be855a66bf72006e3becd497b6)) +* **semantics:** preserve empty string property keys ([#764](https://github.com/morluto/rea/issues/764)) ([630bdaf](https://github.com/morluto/rea/commit/630bdafcacc621105f6a43ed68a6e51651aca629)) +* **semantics:** recognize Node builtin default namespaces ([#769](https://github.com/morluto/rea/issues/769)) ([24a0bf9](https://github.com/morluto/rea/commit/24a0bf9a618604e2fa30817d673ce911c7842819)) +* **semantics:** retain trailing rest argument flows ([#756](https://github.com/morluto/rea/issues/756)) ([4bfd803](https://github.com/morluto/rea/commit/4bfd8038be0a60c43a452bec15e23799a0b040c0)) +* **setup:** accept the pinned Hopper RPM launcher ([#933](https://github.com/morluto/rea/issues/933)) ([dda8e6a](https://github.com/morluto/rea/commit/dda8e6a72890f87bdf8bbd3250b68b6648aae553)) +* **setup:** read an OpenCode V2 server named type as a table entry ([#866](https://github.com/morluto/rea/issues/866)) ([4e17be5](https://github.com/morluto/rea/commit/4e17be57053fb0ce2561d92e05b6e38ef789b2d4)) +* **setup:** register OpenCode V2 configurations in mcp.servers ([957069d](https://github.com/morluto/rea/commit/957069d14d832ddeac3f90dd141d379b81e2832a)) +* **setup:** register OpenCode V2 configurations in mcp.servers ([69d8afe](https://github.com/morluto/rea/commit/69d8afefc19e014a4bba7a182983a9e77e100a75)) +* **target:** decode app Info.plist executables with an XML parser ([c57f1b7](https://github.com/morluto/rea/commit/c57f1b74e0af5cf5fbb459cb247e1448cbf8df1b)) +* **target:** decode app Info.plist executables with an XML parser ([c182f72](https://github.com/morluto/rea/commit/c182f72ad3d9eea10b084a43527acb7c8ca2d6bf)) +* **test:** disconnect Hopper fixture IPC on SIGTERM ([b912b11](https://github.com/morluto/rea/commit/b912b117f019d5d3e4b4e3ef683e9b25f3a709bc)) +* **windows:** accept ordinary drive path separators ([#791](https://github.com/morluto/rea/issues/791)) ([b62931f](https://github.com/morluto/rea/commit/b62931f8335f58bfe344883a4f149e9eef73d835)) +* **windows:** remove cancelled snapshot outputs ([#957](https://github.com/morluto/rea/issues/957)) ([d76f7d1](https://github.com/morluto/rea/commit/d76f7d150895289aa1fb85a057b81ca7ff102df4)) + + +### Performance Improvements + +* **artifacts:** stream ASAR members without full buffering ([#962](https://github.com/morluto/rea/issues/962)) ([abd7a5e](https://github.com/morluto/rea/commit/abd7a5ed091061e4b1e83e5933e9009fb5b8d577)) +* **call-path:** prune dead ends and enumerate without recursion ([#909](https://github.com/morluto/rea/issues/909)) ([e52fe97](https://github.com/morluto/rea/commit/e52fe9719fec03f2aa79b1a1b9e11b38c229903c)) +* **cli:** defer catalog digests and use canonical cache scope ([9a15525](https://github.com/morluto/rea/commit/9a1552529889e9f5b52a88739eb767b92e57e24e)) +* **cli:** defer unused catalog work during command startup ([#920](https://github.com/morluto/rea/issues/920)) ([9a15525](https://github.com/morluto/rea/commit/9a1552529889e9f5b52a88739eb767b92e57e24e)) +* **comparison:** reuse canonical sort keys ([#990](https://github.com/morluto/rea/issues/990)) ([ebb065e](https://github.com/morluto/rea/commit/ebb065e2dc15f5763276f401521896f66bae6339)) +* **dotnet:** index declaring-type ranges once per metadata table ([#918](https://github.com/morluto/rea/issues/918)) ([7a9f587](https://github.com/morluto/rea/commit/7a9f587c3a9998d828b83388438976527ca7074f)) +* **dotnet:** index ordered declaring-type ranges once per table ([7a9f587](https://github.com/morluto/rea/commit/7a9f587c3a9998d828b83388438976527ca7074f)) +* **evidence:** scale imports and dependency graphs ([#941](https://github.com/morluto/rea/issues/941)) ([7dc40d3](https://github.com/morluto/rea/commit/7dc40d3d9e00caf013109942a5d9a1d7fdf9ec62)) +* **inspector:** bound runtime script location authorization ([#945](https://github.com/morluto/rea/issues/945)) ([58381ee](https://github.com/morluto/rea/commit/58381eefa24f73580e31eaa09dc14b9c04d78d6c)) +* **javascript:** analyze member chains without recursive copying ([#932](https://github.com/morluto/rea/issues/932)) ([dcde958](https://github.com/morluto/rea/commit/dcde9584508830a5d99ac5e2032f539563c69002)) +* **javascript:** index Promise return-site ownership ([#971](https://github.com/morluto/rea/issues/971)) ([2cedde9](https://github.com/morluto/rea/commit/2cedde9f1cf72b04bef37f9f004bb68d5ff6502a)) +* **javascript:** index semantic fingerprint relations ([#942](https://github.com/morluto/rea/issues/942)) ([c92a344](https://github.com/morluto/rea/commit/c92a344d4770125d85a11f3fc7a23d800bd30003)) +* **javascript:** reduce analysis and source-map work ([#955](https://github.com/morluto/rea/issues/955)) ([c7530f9](https://github.com/morluto/rea/commit/c7530f9a816d84354516d3ea137f4a0d91d3b8b3)) +* **providers:** assemble fragmented responses once per line ([#911](https://github.com/morluto/rea/issues/911)) ([df7151b](https://github.com/morluto/rea/commit/df7151ba305eab3a33a9261a023705b29862b308)) +* **reconstruction:** index coverage evaluation ([#983](https://github.com/morluto/rea/issues/983)) ([f26c8b5](https://github.com/morluto/rea/commit/f26c8b5b8cff10b93173359b5453874d659f2440)) +* **setup:** reuse initial diagnostics during planning ([#940](https://github.com/morluto/rea/issues/940)) ([118d43e](https://github.com/morluto/rea/commit/118d43e787e38857714852cc846b1b604a61950d)) + + +### Code Refactoring + +* add incremental capability migration safeguards ([#819](https://github.com/morluto/rea/issues/819)) ([75f50d8](https://github.com/morluto/rea/commit/75f50d86f6d504f4dd97a707871d39ec022b3047)) +* **android:** group layered source and verification ([#873](https://github.com/morluto/rea/issues/873)) ([8e8921d](https://github.com/morluto/rea/commit/8e8921d40c63b40ab7e1598f672063dd304d93f0)) +* **apple:** group artifact producers and verification ([#952](https://github.com/morluto/rea/issues/952)) ([ef1a0cc](https://github.com/morluto/rea/commit/ef1a0cc65af7918ef7204a39f42a4299b486c453)) +* **artifacts:** isolate JavaScript file acquisition ([#916](https://github.com/morluto/rea/issues/916)) ([f0a68dc](https://github.com/morluto/rea/commit/f0a68dce60e81b4d2f420bf774b61957a37326ac)) +* **binary:** group session selection and snapshot ownership ([#886](https://github.com/morluto/rea/issues/886)) ([dd114d3](https://github.com/morluto/rea/commit/dd114d39801973e958a582c8fcba7ccce0e50299)) +* **binary:** move production composition outside application workflows ([#965](https://github.com/morluto/rea/issues/965)) ([33d4761](https://github.com/morluto/rea/commit/33d4761cc7a96630b7c2fed890df83d433c45914)) +* **browser:** align inspector exclusions with canonical vocabulary ([#834](https://github.com/morluto/rea/issues/834)) ([de9593a](https://github.com/morluto/rea/commit/de9593ad48c0b97507f18949b7ad86ad9c743ebc)) +* **cli:** split core analysis command registrar ([#858](https://github.com/morluto/rea/issues/858)) ([8b9940a](https://github.com/morluto/rea/commit/8b9940a7b5d3fcd5a8eda6d397f0ab7c3aaf2ac9)) +* colocate investigation record implementation ([#867](https://github.com/morluto/rea/issues/867)) ([bb19c94](https://github.com/morluto/rea/commit/bb19c9477ef3eefd9c5b2e6cabe78cc94e6779ae)) +* compose investigation record ownership ([#845](https://github.com/morluto/rea/issues/845)) ([ce73c3f](https://github.com/morluto/rea/commit/ce73c3fed2e5fd20ae0113579345a69763eb7655)) +* declare auxiliary capabilities from source ([#853](https://github.com/morluto/rea/issues/853)) ([4a6c17c](https://github.com/morluto/rea/commit/4a6c17ce83dfd20412f0e168525314a20c61169e)) +* **domain:** consolidate property-key extraction behind semanticStaticPropertyKey ([#823](https://github.com/morluto/rea/issues/823)) ([764ff05](https://github.com/morluto/rea/commit/764ff05231f1a666dfa655492da42b4b6ba742c3)) +* **domain:** converge comparison mechanics on shared vocabulary ([#824](https://github.com/morluto/rea/issues/824)) ([c13c9d4](https://github.com/morluto/rea/commit/c13c9d41f8441a711266adee21429fb7433a6aa1)) +* **domain:** extract artifact path syntax helpers ([#830](https://github.com/morluto/rea/issues/830)) ([ef535b8](https://github.com/morluto/rea/commit/ef535b81fefc358b0bdb655e5aa057fc3949ce5f)) +* **domain:** extract source-map envelope walk and disambiguate stringValue ([#828](https://github.com/morluto/rea/issues/828)) ([64ebdf4](https://github.com/morluto/rea/commit/64ebdf45016bda679674cbd2287576eadf66a1fc)) +* **domain:** map validation taxonomies and add inventory Result seam ([#833](https://github.com/morluto/rea/issues/833)) ([78f4848](https://github.com/morluto/rea/commit/78f4848d70ce462426e9f221d6e7b3e34219bf46)) +* **domain:** split Interface Builder keyed-archive parse from graph builder ([#859](https://github.com/morluto/rea/issues/859)) ([da09706](https://github.com/morluto/rea/commit/da0970672da725dbb095ffcaefad07303ae85aab)) +* **firmware:** group layered source and verification ([#878](https://github.com/morluto/rea/issues/878)) ([507e8b3](https://github.com/morluto/rea/commit/507e8b3c7be75f8de69be214618394aca3585404)) +* **ghidra:** extract createClient and load-image attestation ([#856](https://github.com/morluto/rea/issues/856)) ([b7df5b7](https://github.com/morluto/rea/commit/b7df5b78052539e57967db8e6562befd70c6ebdc)) +* give Inspector its own capability directory ([#869](https://github.com/morluto/rea/issues/869)) ([021ba69](https://github.com/morluto/rea/commit/021ba690b07eab4b990be47f90946db9f70e0fc0)) +* **javascript:** group application workflows and verification ([#896](https://github.com/morluto/rea/issues/896)) ([0804722](https://github.com/morluto/rea/commit/0804722cdf721a8d767d02f9342ebdf8d186f7e1)) +* **javascript:** group domain semantics and contracts ([#907](https://github.com/morluto/rea/issues/907)) ([f6a58f1](https://github.com/morluto/rea/commit/f6a58f10629614fb7a6970421218b73d41bce6a5)) +* **managed:** group capability workflows and verification ([#926](https://github.com/morluto/rea/issues/926)) ([28713f9](https://github.com/morluto/rea/commit/28713f9b4dcb882f0e811653890fde70cb5fdadb)) +* **native:** group analyst semantics workflows and contracts ([#973](https://github.com/morluto/rea/issues/973)) ([ea4db4c](https://github.com/morluto/rea/commit/ea4db4c9f7cbc10dc701c4d5770109c08952a5b8)) +* **native:** split Apple dispatch metadata decode facets ([#855](https://github.com/morluto/rea/issues/855)) ([676959e](https://github.com/morluto/rea/commit/676959ecb55b606a567af3d2eb3d3edc96f0aa2f)) +* **provider:** declare deep capabilities from source ([#892](https://github.com/morluto/rea/issues/892)) ([b808e2d](https://github.com/morluto/rea/commit/b808e2d6bbbae38c1ff2e293e1cf77464d889bdd)) +* share Android and firmware provider factories ([#826](https://github.com/morluto/rea/issues/826)) ([3ed59b2](https://github.com/morluto/rea/commit/3ed59b281247f2180b3c79e658078507e4cc1be2)) + + +### Documentation + +* add concise lawful-use disclaimer ([#888](https://github.com/morluto/rea/issues/888)) ([4037c65](https://github.com/morluto/rea/commit/4037c6545dc8cc47f4602d8f06e5329ed641cdbb)) +* add DX-Ball reconstruction showcase ([#876](https://github.com/morluto/rea/issues/876)) ([895079d](https://github.com/morluto/rea/commit/895079dd093f02487789d9aa967f0cccbfe379d8)) +* add prominent website and showcase links to READMEs ([2ccc86c](https://github.com/morluto/rea/commit/2ccc86cd13d71f08b553d3b9962175144b882539)) +* add README star history chart ([#850](https://github.com/morluto/rea/issues/850)) ([11f27de](https://github.com/morluto/rea/commit/11f27deddf88703368942d3ddfc239a7ebe72e3e)) +* add website and showcase links to READMEs ([#930](https://github.com/morluto/rea/issues/930)) ([2ccc86c](https://github.com/morluto/rea/commit/2ccc86cd13d71f08b553d3b9962175144b882539)) +* **apple:** describe directory-based framework plist ambiguity ([6d74992](https://github.com/morluto/rea/commit/6d74992efed3271e8370c505afdde347609f8353)) +* audit and improve localized readmes translations and wording ([00a6ac3](https://github.com/morluto/rea/commit/00a6ac3de99f432bd6dbb88148a2aab5f8d3d338)) +* clarify NativeAOT scope and regenerate conformance ledger ([c945931](https://github.com/morluto/rea/commit/c945931961cbd488163c6d92ef91336ed506942e)) +* fix stale IDA roadmap item, mermaid paths, toolchain wording ([d2aed17](https://github.com/morluto/rea/commit/d2aed172ab79d98ff4e62c14d8f34cc49c1d17b4)) +* move quick start up and mark skill install optional ([554e9b1](https://github.com/morluto/rea/commit/554e9b1612f7799c2bc7177eb1a9b04a0f27d2e9)) +* note Cloud Agent Node.js toolchain ([#837](https://github.com/morluto/rea/issues/837)) ([6aa6350](https://github.com/morluto/rea/commit/6aa6350c800796d0953937e894ff176ae248e930)) +* publish VitePress documentation on GitHub Pages ([c78fe69](https://github.com/morluto/rea/commit/c78fe6990bcface6853ac5c11c90df6bc0df562d)) +* separate ambiguous and unavailable provider recovery ([c3f2b8e](https://github.com/morluto/rea/commit/c3f2b8e7742915dc27ab7c801a8a1a9127169896)) +* show task-scoped readiness and provider preference in the quick start ([880cca1](https://github.com/morluto/rea/commit/880cca1bca497bb53c97a821b470df0f2235e043)) +* show task-scoped readiness and provider preference in the quick start ([5fd0db3](https://github.com/morluto/rea/commit/5fd0db37d94f1c1a0495920c79e121d799da0d30)), closes [#726](https://github.com/morluto/rea/issues/726) +* update release boundary to npm 4.1.0 with Windows native bundle ([69be514](https://github.com/morluto/rea/commit/69be514c67c8b2eb8b450355a9782d9237748fda)) +* **website:** add Notion case study and improve onboarding ([a6ba772](https://github.com/morluto/rea/commit/a6ba772c08e2648e6602787a0ea537d75d72802d)) +* **website:** add TH04 bullet-ring case study ([f8d84da](https://github.com/morluto/rea/commit/f8d84da127fad871050c098b004a503b36795ad0)) +* **website:** add worked guides and keep Pages publishing manual ([#925](https://github.com/morluto/rea/issues/925)) ([4f25ee3](https://github.com/morluto/rea/commit/4f25ee33dcd4195f8d877f8835f81c4e08c95235)) +* **website:** clarify guides and case studies ([5065fcd](https://github.com/morluto/rea/commit/5065fcd5205ec7c0d259131431c2cf54d9f69cf6)) + + +### Tests + +* **artifacts:** report compiled connector records when a storyboard route is missing ([d421d3f](https://github.com/morluto/rea/commit/d421d3f286ce515b5126859a0f9cac60667a168e)) +* **artifacts:** report observed action routes when a compiled route is missing ([c60b737](https://github.com/morluto/rea/commit/c60b737f7db8e2c9a1982cdb4357fcda1f47ff08)) +* **artifacts:** write the storyboard fixture's action in Cocoa syntax ([53bfa7e](https://github.com/morluto/rea/commit/53bfa7e3f792cdb9f2e95fc954f3616426414bcc)) +* eliminate fixture races and prune redundant scaffolding ([#849](https://github.com/morluto/rea/issues/849)) ([f72931d](https://github.com/morluto/rea/commit/f72931d9dd54609bfca422750fc2667f80af691c)) + + +### Continuous Integration + +* consolidate macOS native baseline checks ([#935](https://github.com/morluto/rea/issues/935)) ([810fe53](https://github.com/morluto/rea/commit/810fe53e799b89dc215c52c9245d36fb2a44d9c4)) +* verify native arm64 and Intel macOS workflows ([#898](https://github.com/morluto/rea/issues/898)) ([4801a24](https://github.com/morluto/rea/commit/4801a24089964860db7f4855be21d2d4313c7eab)) + ## [4.1.0](https://github.com/morluto/rea/compare/rea-agents-4.0.1...rea-agents-4.1.0) (2026-10-06) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07b182bd2..36d442515 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ When adding or changing an MCP tool, follow the [tool design guide](docs/tool-de ## Development setup -REA development requires Node.js 24.18.x and npm 11.16.x (pinned toolchain via `nvm use`; the supported runtime range is Node.js ^22.19 || ^24.11 || >=26, as the README badge states). Real-Hopper verification additionally requires either macOS 12+ or an officially supported Linux host (Ubuntu 24.04+, Fedora 41+, or 64-bit Arch) and an installed Hopper application. Linux demo verification uses its own private Xvfb display and does not require a desktop session. Run `nvm use` before installing dependencies. +REA development requires Node.js 24.18.x and npm 11.16.x (pinned toolchain via `nvm use`; the supported runtime range is Node.js ^22.19 || ^24.11 || >=26, as the README badge states). Real-Hopper verification additionally requires either macOS 12+ or an officially supported Linux host (Ubuntu 24.04+, Fedora 41+, 64-bit Arch, or CachyOS) and an installed Hopper application. Linux demo verification uses its own private Xvfb display and does not require a desktop session. Run `nvm use` before installing dependencies. ```bash npm ci @@ -28,19 +28,21 @@ Keep dependencies flowing inward through the existing domain, contracts, provide ## Documentation website -The VitePress site uses the Markdown files in `docs/` and deploys to -. Run `npm run docs:dev` for live editing, -`npm run docs:build` to check the production build and links, and -`npm run docs:preview` to preview that build at `/rea/`. +The VitePress site uses the Markdown files in `docs/`. Run `npm run docs:dev` +for live editing, `npm run docs:build` to check the production build and +links, and `npm run docs:preview` to preview that build at `/rea/`. Site navigation lives in `docs/.vitepress/config.ts`. Keep links to guides relative so they work on GitHub and the website; link to repository files outside `docs/` using their full GitHub URLs. Generated reference documents still use `npm run docs:generate`; `docs:build` only builds the website. -The documentation workflow builds pull requests and deploys changes on `main`. -The repository's **Settings → Pages → Build and deployment → Source** must be -set to **GitHub Actions** before the first deployment. +Pull requests run `npm run docs:check`. `.github/workflows/pages.yml` is a +manual VitePress build and does not publish. The public site at + is published only by the manual website +workflow on `main`. The repository's **Settings → Pages → Build and +deployment → Source** must be set to **GitHub Actions** before the first +deployment. ## Development feedback and PR verification @@ -120,32 +122,14 @@ Describe the behavior change and verification performed in the pull request. Nev ## Maintainer release checklist -Run `npm run check:pr`, the isolated package verifier, package dry run, and two-target real-Hopper verifier described above. Build a local tarball and exercise the executable through the package boundary: +Use the [checkpoint release guide](docs/releasing.md). Releases start from an +explicit `release/VERSION` branch cut at a recorded commit. Manually prepare +the bot PR, wait for its exact-head CI and review, merge into that frozen +branch, and manually publish through the official Release workflow. Main +pushes do not update release PRs or publish packages. Both publishers build +the exact SHA tagged by Release Please. -```bash -npm pack -``` - -Use the exact filename printed by `npm pack` to run the packaged executable: - -```bash -npm exec --yes --package ./rea-agents-VERSION.tgz -- rea --help -``` - -Replace `VERSION` with the packed version; do not use a tarball from an earlier -build. - -Publish the public package: - -```bash -npm publish --access public -``` - -After npm registry propagation, verify the published CLI and connect the client SDK version pinned in `package.json` to the published server to confirm the canonical tool catalog: - -```bash -npx -y rea-agents@latest --help -npx -y rea-agents@latest doctor -npx -y rea-agents@latest setup --yes --all-detected -npx -y rea-agents@latest mcp -``` +Keep new implementation commits on main for the next release. The workflow +owns packaged-artifact verification, npm publication, the published CLI/MCP +canary, and MCP Registry publication. Sync release metadata back to main after +publication; see the guide for partial-publication recovery and verification. diff --git a/README.md b/README.md index 7e4f91f40..d2ec86629 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,17 @@ [![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents) [![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml) -[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](#tool-catalog-for-investigation) +[![MCP tool catalog](https://img.shields.io/badge/MCP-tool_catalog-5c4ee5?style=flat-square)](docs/product-catalog.json) [![Node.js 22+](https://img.shields.io/badge/Node.js-22.19%2B-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/) +[![skills.sh](https://skills.sh/b/morluto/rea?style=flat-square)](https://skills.sh/morluto/rea/reverse-engineer-anything) [![MIT license](https://img.shields.io/badge/license-MIT-f4c430?style=flat-square)](LICENSE) [![Discord](https://img.shields.io/discord/1556595354999332884?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/GkcryMnJDM) morluto%2Frea | Trendshift -**[Website](https://morluto.github.io/rea/) · [Guides](https://morluto.github.io/rea/guides/) · [DX-Ball showcase](https://morluto.github.io/rea/showcase/dx-ball/)** +**[Website](https://morluto.github.io/rea/) · [Guides](https://morluto.github.io/rea/guides/) · [Showcases](https://morluto.github.io/rea/showcase/)** -[Quick start](#quick-start) · [Current status](#current-status) · [Investigation model](#the-investigation-model) · [Tool catalog](#tool-catalog-for-investigation) · [Roadmap](#roadmap) · [How it works](#how-it-works) +[Quick start](#quick-start) · [How REA works](#how-rea-works) · [What you can analyze](#what-you-can-analyze) · [Showcases](#showcases) · [FAQ](#faq) · [Documentation](#documentation) npx rea-agents setup @@ -53,760 +54,263 @@ REA connects your agent to tools for inspecting native binaries, JavaScript and Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper or Ghidra installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs neither engine. +> **[Visit the REA website](https://morluto.github.io/rea/)** for setup instructions, illustrated guides, and real case studies. + ## Quick start -### Run setup (recommended) +### Set up your agent -Set up REA with your agent: +With Node.js and npm installed, run: ```bash npx rea-agents setup ``` -Choose which supported agents should use REA, then review the exact paths and changes before approving. Existing REA registrations are selected by default; newly detected agents are available to select, but detection alone does not select them. Setup adds MCP access and REA's guided workflow for selected agents. Hopper is a separate optional choice with its own consent. Setup can also record an existing Ghidra installation. +Choose your agents, review the proposed changes, and approve them. Setup adds +REA's MCP server and matching workflow instructions, with backups of existing +configuration. Restart your agent afterward. -Setup shows its changes before applying them and backs up existing configuration. See [Installation and setup](docs/installation.md) for requirements and setup options. +Setup supports Claude Code, Codex, Cursor, Gemini CLI and +[other agents](docs/installation.md#supported-agents). See +[installation and setup](docs/installation.md) for provider configuration and +manual MCP registration. -### AI Coding Assistants (optional) - -Add the skill to your AI coding assistant for richer context: - -```bash -npx skills add morluto/rea --skill reverse-engineer-anything -``` - -The skill provides REA's investigation workflow. Run setup above to connect REA to your agent and configure analysis tools. Setup already installs a version-matched skill by default; this command installs the repository version. - -### With an agent (recommended) - -After setup, restart your agent and [describe the app or feature](#just-ask-your-agent) you want to understand. Hopper can run in demo mode; if it shows a first-run prompt, choose the demo or enter an existing license. - -REA supports Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, and VS Code. Existing REA registrations are selected by default during setup; other detected agents remain unselected until chosen. Other agents can use the [manual MCP configuration](#manual-mcp-configuration). - -### First result from the terminal - -For your extracted JavaScript/Electron application tree or ASAR, run: - -```bash -npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json -``` - -Replace the path with your target (for example, `"D:/apps/example"` on Windows). -This returns inline Evidence, recovered graph, limitations, and unknowns without -MCP setup, Hopper, Ghidra, or executing the application. For a native app, configure -its engine first, then use `analyze` with that app's path. Run `doctor` when you -need diagnosis; it is not a prerequisite for each analysis. - -### Check readiness for the task at hand - -`rea doctor` without options is an audit of the whole integration. It checks -every detected agent registration, the installed skill, and every optional -analysis engine, so it can report `healthy: false` while your current task works. -Choose a readiness scope for the work you are doing: - -| Task | Readiness check | -| ----------------------------------- | -------------------------------------------------------------------------------------------- | -| Static JavaScript/Electron analysis | None. Run `analyze-javascript-application` directly. | -| Troubleshoot one analysis engine | `rea doctor --provider ghidra --json` (or `hopper`, `ida`) | -| Check one agent's MCP registration | `rea doctor --client codex --json` (see [client IDs](docs/installation.md#supported-agents)) | -| Check the installed skill | `rea doctor --skill --json` | - -A scoped report has `scope.mode: "explicit"`. Only `scope_checks` determine -`healthy` and the exit status. Everything else is listed in -`informational_checks`; you don't need to fix it for this task. For example, a -missing engine you are not using needs no repair. `environment_healthy` still -summarizes the full audit. `--target` adds a target check, but without a scope -option the report remains audit-wide. - -When more than one installed engine supports a native target, REA does not pick -one: opening the target fails with `code: "capability_unavailable"`, -`details.selection_reason: "ambiguous"`, and the providers in -`details.candidate_ids`. Choose one once: pass `--provider` on -the CLI or `provider_id` on `open_binary`, or set `REA_ANALYSIS_PROVIDER` as a -standing preference. An explicit selector overrides the environment variable. -The session keeps that choice and never falls back to another engine. -Recovery depends on `details.selection_reason`. For `ambiguous`, choose one of -the candidates. For `provider_unavailable`, the engine you selected needs repair, -so run `rea doctor --provider --json` and follow its remediation. Neither -reason means you have to install every engine. See -[Choosing a deep-analysis provider](#choosing-a-deep-analysis-provider). - -### Install the rea command - -Install the command-line interface: - -```bash -curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash -``` - -The installer adds `rea` to your system and starts setup when run in a terminal. It requires Node.js and npm to be installed already. - -Alternatively, install with npm, then run setup: - -```bash -npm install --global rea-agents -rea setup -``` - -Update either installation with `rea update`. - -### Requirements - -Static JavaScript inspection requires the Node/npm runtime only. Host and -external-tool prerequisites depend on the selected workflow; the native -provider guides describe their supported platforms. - -- macOS 12 or newer -- Ubuntu 24.04+, Fedora 41+, or 64-bit Arch Linux -- Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ -- npm; REA does not require or install a particular npm version - -Deep native binary analysis requires [Hopper](https://www.hopperapp.com/), [Ghidra](#ghidra-analysis-provider), or [IDA Pro](#ida-pro-analysis-provider). Hopper is separate software with its own license; its demo supports analysis with vendor-defined limits. Ghidra and IDA are bring-your-own providers. - -Firmware region inspection and explicit extraction use caller-supplied Binwalk and Unblob on Linux. See [Firmware analysis](docs/firmware-analysis.md) for setup, provenance, resource limits and native handoff. - -Static APK analysis uses a separately supplied headless JADX JAR and a full JDK, with no emulator or APK execution. See [Android analysis](docs/android-analysis.md) for setup, CLI/MCP operations, coverage and public test fixtures. Authenticated IPA and macOS `.app`, ZIP, or DMG inventory Evidence can be projected into bundle anatomy, such as XPC services, app extensions, login items, privileged helpers, and launchd plists, with [Apple application analysis](docs/apple-application-analysis.md). - -Repository main and npm 4.1.0 include experimental Windows x64 Ghidra support for native x86-64 PE applications on local NTFS, with bundled Job Object, private-DACL, and path-admission controls. Check the [release boundary](docs/installation.md#released-package-and-main) before expecting this from an older npm package. See [Windows Ghidra P0](docs/windows-ghidra-p0.md) for prerequisites and verified scope. - -If something is not working, run: - -```bash -npx -y rea-agents@latest doctor -``` - -`doctor` checks your host, dependencies, analysis tools, and agent configuration without changing them. Use `--json` for structured diagnostics. - -### Linux installation and troubleshooting - -On macOS, setup can install Hopper in `~/Applications` after approval. It verifies the official download and does not need Homebrew or administrator privileges. - -On supported Linux distributions, setup can install Hopper and its demo-session dependencies through your system package manager. You may see a system authorization prompt. Demo sessions use a private virtual display, leaving your desktop alone. See [Hopper installation](docs/installation.md#hopper) for download verification and platform details. - -The normal Linux launcher is `/opt/hopper/bin/Hopper`. If Hopper was installed elsewhere: - -```bash -export HOPPER_LAUNCHER_PATH=/absolute/path/to/Hopper -rea doctor --json -``` - -If doctor reports a missing analysis engine even though the file exists, inspect shared-library resolution with: - -```bash -ldd /opt/hopper/bin/Hopper | grep 'not found' -``` - -Install the missing packages and rerun `rea setup`. The Linux demo needs Xvfb, Python 3, X11, and XTEST; approved setup installs these dependencies. If you use the curl installer, add `~/.local/bin` to your shell `PATH` when needed. - -REA defaults `HOPPER_LAUNCHER_PATH` to `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper` on macOS and `/opt/hopper/bin/Hopper` on Linux. Explicit configuration always takes precedence. - -### Ghidra analysis provider - -Already use Ghidra? REA can connect it to your agent on Linux x64 or macOS x64/arm64. It accepts **Ghidra 12.1.x** and the **64-bit full JDK** that installation declares (`application.java.min` through `application.java.max`). Current 12.1 releases require JDK 21 or newer and set no maximum. The bridge is verified with Ghidra 12.1.4 and JDK 21. On macOS, your Ghidra installation must also include the native decompiler for your architecture. - -Set the installation paths, then run setup: - -```bash -export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC -export JAVA_HOME=/absolute/path/to/jdk-21 # optional when java and javac resolve from PATH -rea doctor --json -rea setup -rea providers --json -``` - -Setup checks the installations and saves their paths in your selected agents' configuration after approval. Ghidra and Java must already be installed; REA does not download or change them. - -The adapter exposes inventory, function, memory and load-image inspection, plus atomic function annotation edits on Linux and macOS. `annotate_native_function` edits names and entry comments in the session database and returns a refreshed function dossier; executable bytes stay unchanged. Ghidra does not provide GUI controls through REA. - -Opening a Ghidra target selects its provider; the first analysis query starts import and auto-analysis. That query can take longer than a client's default request deadline. See [first-query deadlines and recovery](docs/mcp-contracts.md#ghidra-first-query-deadlines-and-recovery) for an SDK example and cancellation recovery. - -REA analyzes a temporary copy of the target and removes the temporary project when the session closes. Results identify what Ghidra observed and what it could not resolve. Decompilation produces pseudocode rather than the original source. - -Ghidra also imports DOS MZ executables with an explicit 16-bit x86 real-mode profile. Function results include complete observed body ranges, distinguishing owned bytes from the enclosing span. See the [DOS analysis guide](docs/ghidra-dos.md) for addresses, packing, and verification boundaries. -See [optional NativeAOT metadata recovery](docs/ghidra-nativeaot.md) for the pinned -headless adapter, supported layout and existing native-tool workflow. - -Windows Ghidra P0 uses bundled native controls for its read-only native x86-64 PE boundary on local NTFS; see the [Windows Ghidra P0 guide](docs/windows-ghidra-p0.md). See [Ghidra installation](docs/installation.md#ghidra), [provider evaluation](docs/provider-evaluation.md), and [testing](docs/testing.md) for configuration details, coverage, and real-provider verification. - -To remove only REA-owned MCP registrations and the managed skill: - -```bash -rea uninstall -rea uninstall --purge-data # also removes only ~/.rea/cache and ~/.rea/state -``` - -Uninstall preserves Hopper, Node.js, Evidence files, captures, unrelated skills, and other MCP servers. It refuses malformed client configuration and never follows purge-data symlinks. - -### IDA Pro analysis provider - -Already have [mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp) working? REA can reuse its MCP registration for read-only analysis of the current GUI target, or use its database supervisor to open and analyze a supplied binary headlessly. - -```bash -export REA_IDA_MCP_CONFIG=/absolute/path/to/ida-mcp.json -rea function /absolute/path/to/program main --provider ida --json -``` - -The registration selects `attached` (default, legacy 1.4 tools) or `headless` (modern database supervisor). Setup can preserve this file reference in your selected agent's REA registration. REA adapts existing analysis contracts and manages its own headless database; it leaves an attached GUI database open and never saves it. Results stay live because external IDA database changes are not immutable snapshots. - -See the [IDA provider guide](docs/ida-provider.md) for upstream installation links, exact configuration examples, Windows-native headless operation, lifecycle cleanup, and coverage. The initial real workflows cover a Windows GUI and Windows x64 headless IDA 9.3; other engine/platform combinations remain unverified. Use a package version that includes this adapter; repository main can lead the npm release. - -### CLI or agent? - -| If you want to… | Use | -| ---------------------------------------------------------------- | ------------------------------------------------------------------- | -| Ask an agent to investigate an app and build a feature | Run setup, restart your agent, then describe the task | -| Inspect or decompile one part of an app from the Terminal | `rea analyze` or `rea decompile` | -| Validate, canonicalize, or compare Evidence bundles | `rea evidence-import`, `rea evidence-export`, or `rea compare` | -| Map a local JavaScript/Electron application without executing it | `rea analyze PATH` or `rea analyze-javascript-application` | -| Reuse immutable analysis results without relaunching a provider | Pass `--snapshot /path/to/analysis.json` to a deep-analysis command | -| Import source as historical reference | `rea import-reference-source` | -| Capture or compare controlled process behavior | `rea capture-process` or `rea compare-process-captures` | - -```bash -rea evidence-import /absolute/path/to/evidence/bundle.json -rea evidence-export /absolute/path/to/evidence/bundle.json /absolute/path/to/evidence/canonical.json -rea compare /absolute/path/to/evidence/left.json /absolute/path/to/evidence/right.json -``` - -Analyze a JavaScript application directory or ASAR without executing it: - -```bash -rea analyze /absolute/path/to/releases/app.asar --json -rea analyze-javascript-application /absolute/path/to/releases/app.asar --json -``` - -For a directory or `.asar`, generic `rea analyze` automatically selects the -static JavaScript application provider when neither `--provider` nor -`--snapshot` is supplied. Both routes return the analysis and its Evidence -context inline. - -Import an older source tree as a reference. REA keeps it separate from observations of the current app: - -```bash -rea import-reference-source /absolute/path/to/source -``` - -Historical-source import requires safe no-follow file opens and currently runs -on Linux and macOS. Native Windows reports `unsupported_host`; run the import -with Linux REA in WSL or on another supported host. Changing permissions or -reinstalling REA does not enable this Windows workflow. - -Imports read the path supplied to the command. File names do not cause automatic omissions; files are represented by hashes and metadata. To exclude selected paths, set `REA_REFERENCE_SECRET_PATTERNS_JSON` to a JSON string array of ignore patterns. Exports never replace an existing file unless `--overwrite` is explicit. - -Use a snapshot to save successful analysis results and reuse them on later runs. REA reuses a result only when the target bytes, operation, parameters, analysis tool, and settings match. It does not cache changes or cursor-dependent calls. Snapshot files stay local and use owner-only permissions. - -```bash -rea analyze /absolute/path/to/app --snapshot /absolute/path/to/analysis/app.json -# The same exact query can be answered from that snapshot. -rea analyze /absolute/path/to/app --snapshot /absolute/path/to/analysis/app.json -``` - -Exact CLI cached-evidence reads happen before any provider process starts. In MCP sessions, -pass `snapshot_path` to `open_binary` to import a snapshot atomically while -opening its matching target; MCP providers may still start before a cached call -result is returned. Pass `snapshot_path` and, when required, `overwrite: true` to -`close_binary` to save atomically before Hopper resources are released. If the -save fails, REA deliberately leaves the session open. - -## Just ask your agent - -After [setup](#quick-start), restart your agent and ask: +### Ask your agent ```text Understand how search works in the Notes app, show me the evidence, and build a similar feature for my project. ``` -Replace Notes with the app you want to understand, or ask for an overview first. +Replace Notes with your target app and the feature you want to understand. -## The investigation model +### Use the terminal - - - - - - -
-Decompile

-Open an app and recover readable code, strings, names, and other clues about how it works. -
-Understand

-Follow the code from one part of the app to another until the agent can explain how a feature actually works. -
-Recreate

-Turn what the agent learned into a feature for your own product, adapted to your stack, interface, and requirements. -
- -REA shows how it reached its conclusions. It does not claim to recover original source code or automatically clone an application. - -## Why REA - -| | | -| ------------------------ | ----------------------------------------------------------------------------------------------------- | -| **Built for agents** | Ask what an app does and let your agent inspect it instead of guessing. | -| **CLI and MCP** | Run the same reverse-engineering capabilities from your terminal or agent. | -| **Guided setup** | Configure your agent, connect an existing analysis tool, or install Hopper with your approval. | -| **From insight to code** | Understand a feature, then build your own version in the same coding session. | -| **Local by design** | Analysis runs on your supported local host. REA does not upload the app to a hosted analysis service. | -| **Keeps context** | Investigate several apps without starting over for every question. | - -## One prompt, a full investigation - -```text -Reverse engineer the Notes app. Find how offline search works, explain it, -and build a version for my project using TypeScript and SQLite. -``` - -REA gives the agent a clear path from that request to working code: - -| Step | What the agent does | REA tools | -| ---: | --------------------------------------- | ---------------------------------------------------------------- | -| 1 | Opens and identifies the binary | `open_binary`, `binary_overview` | -| 2 | Finds likely offline-search clues | `search_strings`, `search_procedures`, `list_names` | -| 3 | Connects those clues to executable code | `find_xrefs_to_name`, `xrefs`, `procedure_callers` | -| 4 | Reconstructs the relevant control flow | `get_call_graph`, `procedure_callees`, `procedure_info` | -| 5 | Decompiles the relevant routines | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` | -| 6 | Builds the feature in your project | code adapted to your stack, product, and requirements | - -REA handles the app analysis in steps 1 through 5. The agent performs step 6 with its normal file-editing and test tools, using what it learned about the app. - -## Showcase - -### DX-Ball: game reconstruction (in progress) - -[DX-Ball](https://github.com/N0zoM1z0/dx-ball) follows the journey from a classic -Windows game's executable to maintainable C. Using REA's Ghidra provider, the -project traces functions, game state and dependencies, then checks reconstructed -behavior with original-x86 differential tests and pinned-compiler replay. - -A [sound-pan investigation](https://github.com/N0zoM1z0/dx-ball/blob/main/docs/GAMEPLAY_OWNER.md) -turns incomplete pseudocode into a C implementation that passes 3,205 original-x86 -cases and reproduces all 63 compiled function bytes. Follow its -[REA workflow](https://github.com/N0zoM1z0/dx-ball/blob/main/docs/REA.md) -from binary evidence to reconstruction. - -## What agents can do - -- Investigate a feature you like and build a version tailored to your own product. -- Explain how a feature works when its source code is unavailable. -- Reconstruct an app's authentication, storage, update, or networking flow. -- Recover enough structure to document an undocumented format or interface. -- Trace a suspicious behavior from a string or symbol to the code that implements it. -- Turn recovered behavior into product features, tests, migration notes, ports, or interoperable replacements. -- Analyze Swift and Objective-C metadata without manually untangling every mangled symbol. -- Leave names, comments, and bookmarks in Hopper so human and agent analysis reinforce each other. - -See [native investigation](docs/native-investigation.md) for keyed archives, instruction/call/type primitives, typed dispatch metadata, value traces and native desktop observation. - -## Tool catalog for investigation - -| Tool family | Count | Examples | -| ------------------------- | ----: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Native inspection | 41 | functions, pseudocode, assembly, strings, symbols, calls, references, annotations, byte reads, and file offsets | -| Investigation workflows | 14 | app overviews, function dossiers, native APIs and dispatch, batch decompilation, feature traces, call paths, call graphs, Swift and Objective-C discovery | -| Native macOS utilities | 8 | Mach-O metadata, code signatures, plists, architectures, Swift demangling, and LLDB call observation without launching Hopper | -| Artifact graph | 5 | directory and package inventories, compiled Interface Builder files, Apple asset catalogs, and extraction | -| Managed PE/CLI | 7 | .NET identity, metadata, CIL instructions, native dependencies, reconstruction imports, and build comparisons | -| Firmware | 2 | Linux firmware region inspection and explicit extraction | -| Android APK | 5 | package and manifest declarations, class search, member inventories, method decompilation, and incoming static references | -| Browser observation | 11 | page structure, network metadata, scripts, source maps, WebMCP discovery, screenshots, and capture comparisons | -| Electron analysis | 5 | renderer observation, static app mapping, and static/runtime reconciliation | -| JavaScript runtime | 2 | Node/Electron Inspector target discovery, script locations, and execution-context events | -| Application workflows | 13 | captured website script export; Android/Apple inventory projections; cross-layer feature traces, build comparisons, historical source mapping, static return-shape comparison, and reconstruction checks | -| Workspace and observation | 21 | sessions, evidence bundles, navigation context, process/artifact/function comparisons, and open-question tracking | - -The public interface describes what the agent is trying to learn. Providers decide how to answer. macOS utilities handle common semantic inspection without launching Hopper; Hopper handles deeper native analysis; the process harness records direct behavioral captures. - -## Current status - -REA supports native application, JavaScript, Electron, .NET, and browser investigation on macOS and Linux. Individual tools have platform and runtime prerequisites. `rea capabilities` and `rea providers` describe the binary-session providers and auxiliary operations; they are not an inventory of every browser, Android, or application workflow. Use the connected MCP tool list and `binary_session` tool availability for the full MCP surface, and the relevant guide for each tool's prerequisites. Repository main can be ahead of [the npm release](docs/installation.md#released-package-and-main). - -Static Android APK inspection supports Linux and macOS; the current metadata bridge is verified on macOS arm64 with headless JADX; see [Android analysis](docs/android-analysis.md) for its separate prerequisites and coverage. - -- **Native binaries:** Open Mach-O, ELF, PE, and Mac `.app` targets through a selected deep provider. Hopper and Ghidra cover broad inventory and function analysis; the IDA adapter supplies its documented read-only function/string operations. Hopper also accepts `.hop` databases and supports annotations. -- **Packages and resources:** Inspect directories, ZIP, APK, IPA, ASAR, plists, compiled Interface Builder files, and Apple asset catalogs. Artifact requests name the input and requested extraction or traversal directly; macOS DMG traversal also requires the host's native mounting support. -- **JavaScript and Electron:** Map modules, imports, source maps, routes, IPC channels, storage, and native add-ons without running the app. Compare builds and trace a feature across the recovered graph. Dynamic and ambiguous relationships remain unresolved. See [JavaScript application workflows](docs/javascript-application-workflows.md). -- **Websites:** Inspect a selected page in an existing Chrome-family browser. Capture page structure, network metadata, script evidence, and screenshots requested by the call. Passive observation does not navigate or execute page JavaScript. See [browser observation](docs/browser-observation.md). - - Export retained scripts with `export_web_scripts` / `rea export-web-scripts` - into a verified local directory, then use the existing JavaScript analysis - and tracing tools. Source URLs, frame or transaction references, competing - versions, and missing-source states remain inline. See - [captured website scripts](docs/website-script-export.md). - Inspect a selected node's listener sources or observe an externally triggered - execution window with `inspect_web_event_listeners` / `observe_web_execution`. - Precise coverage resets counters and affects optimized execution; see - [website runtime attribution](docs/web-runtime.md). - Trace one exported script's native imports with `trace_web_module_imports` / - `rea trace-web-module-imports`, preserving query/fragment identity and optional - import-map context. Requires caller-supplied Chromium via - `REA_BROWSER_EXECUTABLE`; see [module relationships](docs/website-module-trace.md). - -- **Source locations:** Trace one retained website script point through a selected - local source map with `rea trace-web-source-location`. See - [captured website source locations](docs/web-source-location.md) for byte - identities, embedded original content and coverage limits. - Recover readable modules from selected local bundles with - `recover_javascript_sources` / `rea recover-javascript-sources`, then pass - the returned `analysis_input` to static analysis. This optional Linux x64 - adapter requires caller-supplied Wakaru 1.13.0; see - [JavaScript source recovery](docs/javascript-recovery.md). - -- **Electron and Node runtime observation:** Inspect selected Electron pages or attach to a Node/Electron V8 Inspector target. Inspector observation records script locations and execution-context events; it does not infer imports, IPC activity, or which modules executed. See [runtime observation](docs/javascript-runtime-observation.md). -- **.NET assemblies:** Inspect metadata and CIL instructions, compare builds, and check declared native dependencies without loading or running the assembly. Imported decompiler output is labeled as analyst inference. See [managed-code analysis](docs/managed-code-analysis.md). -- **Controlled behavior capture:** Run process, browser, or Electron scenarios with the target, actions, and lifecycle declared in each request, then compare the resulting evidence. Missing observations cannot establish that two runs behaved the same way. -- **Evidence and comparison:** Save results with artifact identity, provider, locations, confidence, and limitations. Export or import bundles, compare artifacts and functions, and connect static findings to runtime observations without claiming causality from correlation. -- **Open questions:** Track unresolved findings, contradictions, and follow-up probes. Reconstruction checks report pass, fail, or unknown rather than treating missing evidence as a pass. -- **Guided workflows:** Start six [MCP investigation workflows](docs/mcp-prompts.md) with suggestions based on your current session. - -Windows x64 Ghidra P0 supports native, non-managed, non-DLL x86-64 PE applications on fixed local NTFS with 25 read-only operations. Linux/macOS Ghidra additionally supports atomic session-scoped function names and entry comments. Ghidra has no GUI controls; Windows P0 has no mutation authority. - -### Website observation with CDP - -REA can inspect an already-running Chrome-family browser through a literal loopback CDP endpoint. Each request names the endpoint and target; an optional origin filter can narrow discovery: +Inspect an extracted JavaScript/Electron app directory or ASAR: ```bash -rea list-browser-targets http://127.0.0.1:9222 --json -rea inspect-web-page http://127.0.0.1:9222 TARGET_ID --json +npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json ``` -The eight passive browser tools work through both CLI and MCP. They inspect the selected page without navigating, clicking, or evaluating its JavaScript. Credentials, cookies, authorization headers, and raw payload values are not retained. A request selects whether to include script sources, accessibility text, screenshots, or console and payload summaries. REA cannot observe activity that happened before it attached. See [browser observation](docs/browser-observation.md) for browser startup, capture options, and limits. +The result includes modules, imports, Electron boundaries and their evidence. +Replace the path with your target, such as `"D:/apps/example"` on Windows. -### Controlled browser scenarios - -`capture_browser_scenario` runs a caller-declared sequence of browser actions through -Playwright. Unlike passive observation, it can interact with the page. Each -step records evidence such as screenshots, page structure, navigation, and -network activity. Missing or truncated observations cannot establish that two -runs behaved the same way. - -```bash -rea capture-browser-scenario ./scenario.json --json -``` - -Launch mode owns a temporary browser profile and removes it after terminating -the launched browser. Connect mode accepts one exact loopback CDP target and -disconnects without closing the external browser. The request supplies the -selected executable or endpoint, actions, and any origin or environment -selections needed by the scenario. Scenario JSON contains secret references and -environment-variable names, never secret values. The default capture retains -only the final URL; request `dom`, `accessibility`, or `screenshot` when an -interaction changes the page without navigating. For asynchronous updates, -wait for a page-specific result-ready condition before capture. See the -[browser scenario contract](docs/browser-scenario-contract.md), including its -interaction example. - -### Node and Electron V8 Inspector observation - -Node and Electron runtime observation attaches to an existing Inspector target named in the request: - -```bash -rea list-javascript-runtime-targets http://127.0.0.1:9229 --json -rea observe-javascript-runtime http://127.0.0.1:9229 TARGET_ID \ - --runtime-kind node --json -``` - -REA records script locations and execution-context events without evaluating -code or setting breakpoints. These observations do not establish import -relationships, event activity, IPC, process identity, or Electron roles. See -[Node and Electron runtime observation](docs/javascript-runtime-observation.md) -for the exact coverage. - -Exact package, tool-family, provider, setup-client, schema, and CLI facts are generated from source in [`docs/product-catalog.json`](docs/product-catalog.json). PR CI verifies this catalog, narrative documentation, and generated schemas. - -## Roadmap - -The [current status](#current-status) section describes shipped capabilities. These are the next areas of work. - -### Now - -1. **Keep documentation accurate:** update the generated catalog and documentation checks when tools, providers, setup options, or versions change. -2. **Test more native binaries:** expand architecture and indirect-call coverage across Hopper and Ghidra. - -### Next - -1. **Connect more application layers:** add static extractors and runtime observations to feature traces. -2. **Extend .NET analysis:** improve comparisons of obfuscated assemblies and connect managed findings to verified native analysis. See the [managed-code guide](docs/managed-code-analysis.md). -3. **Compare more runtime behavior:** expand process, protocol, filesystem, reconnect, and version-comparison coverage. - -### Later - -1. **Expand browser and Electron interaction:** add scenario actions beyond the current click and wait operations. -2. **Observe native apps at runtime:** explore LLDB, Frida, system logs, and native API tracing. -3. **Evaluate more tools and targets:** assess Binary Ninja, Rizin, LIEF, Windows-native tools, mobile apps, and firmware. IDA support already ships via the bring-your-own upstream adapter; see the [IDA provider guide](docs/ida-provider.md). - -Setup already lets you choose agent integration and Hopper installation. Support for installing additional analysis tools is future work, described in the [installation roadmap](docs/roadmap.md). - -See [provider evaluation](docs/provider-evaluation.md) for coverage and remaining requirements, and the [native investigation guide](docs/native-investigation.md) for UI, dispatch, and value-flow analysis. - -## Using REA with other agents - -Setup offers supported agent integrations for selection. Existing REA registrations are selected by default; newly detected agents remain unselected until chosen. Any agent that supports local MCP servers can use the configuration below. - -### Manual MCP configuration - - - -```json -{ - "mcpServers": { - "rea": { - "command": "npx", - "args": ["-y", "rea-agents@4.1.0", "mcp"] - } - } -} -``` - - - -Persistent registrations should use one exact package version. `rea setup` -maintains that pin, updates the bundled skill at the same time, and gives Codex -a 30-second startup allowance for a cold package-runner start. `rea update` -installs an exact release and verifies the new executable. It returns an -unapplied maintenance plan for existing REA integrations, with a scoped setup -command to review and approve their changes. Restart affected agents afterward. - -MCP clients that support prompts can also discover six ordered investigation -workflows through `prompts/list`. Their optional identifier arguments use the -current session for bounded `completion/complete` suggestions; see -[Guided MCP prompts and completion](docs/mcp-prompts.md). - -## How it works - -```mermaid -flowchart LR - Agent["Agent"] --> REA["REA
CLI + MCP"] - Terminal --> REA - REA --> Session["Target-bound session router"] - Session --> Registry["Deep-provider registry
deterministic selection"] - Registry --> Hopper["Hopper provider"] - Registry --> Ghidra["Ghidra provider
inventory + function analysis + annotations"] - Registry --> Ida["IDA MCP provider
attached GUI or owned headless database"] - Hopper --> Runtime["Owned provider runtime
deadline + bounded diagnostics + cleanup"] - Ghidra --> Runtime - Ida --> IdaRuntime["Upstream MCP lifecycle
live observations + owned database cleanup"] - IdaRuntime --> Target - Session --> Native["Native macOS provider"] - Session --> Artifact["Artifact graph provider"] - REA --> Browser["Browser CDP provider"] - REA --> Android["Android static provider
headless JADX adapter"] - Android --> Runtime - REA --> Firmware["Firmware providers
Binwalk / Unblob adapters"] - Firmware --> Runtime - REA --> Process["Process capture provider"] - Runtime --> Target["Target software"] - Process --> Target - Native --> Target - Artifact --> Target -``` - -The CLI and MCP server use the same application workflows and evidence contracts. A provider declares which capabilities it supports and the side effects those capabilities may have. Terminal commands are short-lived; an MCP session can retain an active target and evidence ledger for the session. - -## CLI - -The agent workflow above is the easiest way to use REA. For a one-off overview from the Terminal: - -```bash -npx -y rea-agents@latest analyze /Applications/Notes.app -npx -y rea-agents@latest inspect /Applications/Notes.app -npx -y rea-agents@latest search /Applications/Notes.app "offline" -npx -y rea-agents@latest function /Applications/Notes.app 0x1000 -npx -y rea-agents@latest xrefs /Applications/Notes.app 0x1000 -npx -y rea-agents@latest trace /Applications/Notes.app "offline" -npx -y rea-agents@latest compare /absolute/path/to/left-evidence.json /absolute/path/to/right-evidence.json -npx -y rea-agents@latest capabilities -npx -y rea-agents@latest providers -``` - -Run `npx -y rea-agents@latest --help` for direct decompilation, bounded search and -other options. `analyze` and `inspect` share the same overview workflow; -`function`, `xrefs`, and `trace` return the same Evidence envelopes as MCP. - -Or install the `rea` command globally: +To install the `rea` command for regular use: ```bash npm install --global rea-agents rea --help +``` + +For native analysis, configure a provider first. See the +[CLI and Evidence guide](docs/cli.md) for native commands, provider selection, +snapshots and scripting. + +### Update REA + +REA changes quickly, and new releases include frequent bug fixes. Keep your +installation up to date. + +For an npm-installed CLI: + +```bash rea update -rea mcp ``` -REA accepts a Mac `.app` folder directly. If an agent cannot find an app by name, tell it where the app is installed. +To refresh your agent registrations and skill, run the setup command printed +by the update. -### Choosing a deep-analysis provider - -Choose which analysis tool to use from the CLI: +If you use `npx`, update your agent setup with: ```bash -rea providers --json -rea analyze /absolute/path/to/program --provider hopper -REA_ANALYSIS_PROVIDER=hopper rea decompile /absolute/path/to/program 0x1000 +npx rea-agents@latest setup ``` -For MCP, pass the optional selector on `open_binary`: +Review the setup changes and restart your agent. For one-off CLI commands, +use `npx rea-agents@latest` followed by the command. -```json -{ - "path": "/absolute/path/to/program", - "provider_id": "hopper" -} -``` +## How REA works -Use `--provider`, or `provider_id` in MCP, to choose Hopper, Ghidra, or IDA for a target. This choice overrides `REA_ANALYSIS_PROVIDER`. +Your agent calls REA through MCP to inspect the target and trace relevant code. +REA returns findings with their evidence. The agent uses them to ask follow-up +questions, explain the behavior, or write and test an implementation. +CLI commands use the same workflows. -With `auto`, REA selects the only available tool that supports the target. If both are available, specify one before opening the target. The session keeps that choice until you explicitly switch or close it; a failure never silently switches tools. Artifact-only analysis can work without a native analysis tool. +![REA investigation flow: your agent asks about a local target, REA inspects and traces it using analysis tools, and the agent uses the returned code, references and unknowns to explain, implement and test.](website/public/assets/figures/rea-investigation-flow.svg) -Run `rea providers` and `rea capabilities` to check availability and supported operations. Ghidra supports inspection and atomic function annotation edits on Linux and macOS. Its database edits are discarded on close; GUI controls require Hopper. Windows Ghidra P0 supports read-only analysis of native x86-64 PE applications on local NTFS with bundled native controls. +[Open the full-size figure](website/public/assets/figures/rea-investigation-flow.svg). -The session also reports active work and cleanup status. If a caller times out, the analysis tool may still be busy; `analysis_activity` reports that state. A `cleanup_incomplete` result identifies resources whose shutdown or removal could not be verified. See [provider selection and analysis profiles](docs/adr/0001-provider-selection-and-analysis-profiles.md) for session, cache, and process-tracking details. + -### CLI exit status +## What you can analyze -| Status | Meaning | -| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `0` | The operation completed. Results may still include warnings, partial evidence, or unresolved questions. | -| `1` | The operation could not complete, for example because of invalid input, host permission denial, cancellation, or timeout. Structured output reports the reason when available. | -| `128 + N` | The process ended from signal `N`, where the shell or runtime preserves the conventional signal-derived status. | +REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+, plus npm. +Additional tools and host support depend on the target: -`setup --dry-run` returns status `planned` and exits `0`. A setup result with -status `cancelled` also exits `0`. -`setup` returns `1` for `needs_confirmation` or `needs_human` -because configuration is not ready; rerun it after approval or remediation. -`doctor` returns `1` when required checks for its readiness scope are unhealthy. -Unavailable optional provider prerequisites remain visible as informational -diagnostics and do not block setup or unrelated providers. Output format, full envelopes, -filters, and token controls never change the operation status. +| Target | What REA returns | Requirements and guide | +| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](https://morluto.github.io/rea/guides/native/) | +| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; [application analysis](https://morluto.github.io/rea/guides/javascript/) | +| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](https://morluto.github.io/rea/guides/browser/) | +| Saved network captures | Requests, responses, exposed payloads and source locations | HAR; mitmdump on Linux for native mitmproxy captures; [capture guide](docs/web-network-captures.md) | +| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | Static inspection; [managed-code guide](docs/managed-code-analysis.md) | +| Android APKs | Manifest declarations, classes, decompiled methods and references | Headless JADX and a full JDK on Linux/macOS; [Android guide](docs/android-analysis.md) | +| Firmware | Regions, extraction results and native-analysis handoffs | Binwalk / Unblob on Linux; [firmware guide](docs/firmware-analysis.md) | +| Packages and resources | File inventories, digests, plists, Apple bundle anatomy and extracted resources | [Artifact and JavaScript guide](docs/javascript-artifact-reconstruction.md), [Apple applications](docs/apple-application-analysis.md) | +| Process behavior | Terminal output, interactions, exit and filesystem observations, and run comparisons | Linux/macOS with a native PTY; [process capture](docs/process-capture.md) | -When REA feeds a shell pipeline, enable `pipefail` so a downstream formatter -cannot hide its failure: +Static JavaScript and .NET inspection read the supplied files without running +the application. Runtime capture runs or interacts with the selected target +using your user permissions; each runtime guide describes its effects. -```bash -set -o pipefail -rea inspect-artifact ./app.asar --json | jq . > inspection.json -``` + -## Current Hopper provider +Native formats and host support vary by provider. See +[Hopper and Ghidra setup](docs/installation.md#hopper), the +[IDA guide](docs/ida-provider.md), and +[experimental Windows Ghidra support](docs/windows-ghidra-p0.md). +Ghidra also supports [16-bit DOS analysis](docs/ghidra-dos.md). +For provider selection, see the [CLI guide](docs/cli.md#choose-a-provider). +Check [release availability](docs/installation.md#released-package-and-main) +for features added since the latest npm release. -REA starts Hopper when an operation needs it. On macOS, Hopper may bring its window or a dialog to the foreground even though REA requests background startup. Demo or license prompts may need your attention. +## Showcases -Hopper handles one analysis request at a time. Cancelling your wait does not stop work already running inside Hopper; the session reports whether it is still busy. Successful decompilation results are cached until a relevant rename or comment change. +### DX-Ball: reconstruct a sound-pan calculation -Use `rea instructions` when you only need assembly instructions for a function. It avoids decompilation and a whole-program inventory. +Follow a sound call into its position-to-pan helper, inspect the instructions, +and turn incomplete pseudocode into C. The reconstruction passes 3,205 +original-x86 cases and reproduces all 63 compiled function bytes. -Closing a session shuts down REA's bridge and removes its temporary socket directory while preserving a Hopper application you may be using. If cleanup cannot be verified, `close_binary` reports `cleanup_incomplete` and the affected resources. +[Read the case study](https://morluto.github.io/rea/showcase/dx-ball/) · +[Reconstruction repository](https://github.com/N0zoM1z0/dx-ball) -## Process capture +### Notion: trace the Electron clipboard bridge -Process capture runs the exact executable and scenario declared in the request, -with the requested working directory and environment. Filesystem observation -paths select what to snapshot. The process runs with your user permissions; -Process Capture records behavior and is not a security sandbox. +Find the renderer's clipboard API, follow it through preload and IPC into the +main process, and inspect the rich clipboard format. -Capture a scenario or compare two saved Process Capture Evidence records: +[Read the case study](https://morluto.github.io/rea/showcase/notion/) -```bash -rea capture-process ./authority-scenario.json --json > authority-capture.json -rea capture-process ./reconstruction-scenario.json --json > reconstruction-capture.json -rea compare-process-captures authority-capture.json reconstruction-capture.json --json -``` +### TH04: recover a DOS bullet-ring calculation -The comparison reports each observed dimension separately and identifies the -first terminal, interaction, exit, filesystem, or process divergence. -See [Process Capture](docs/process-capture.md) for scenario fields, limits, and -evidence boundaries. +Inspect the original PC-98 game's 16-bit instructions, recover the fixed and +aimed angle calculations, and compare the reconstructed C++ with the +historical compiler output. -Process Capture currently runs on Linux and macOS with a working native PTY -backend. Native Windows capture remains unavailable until the PTY adapter can -verify descendant cleanup; installing or reinstalling its Windows PTY binary -does not enable capture. For Linux commands, run Linux REA inside WSL. -Comparing existing capture Evidence remains available on Windows. +[Read the case study](https://morluto.github.io/rea/showcase/th04/) · +[Reconstruction repository](https://github.com/N0zoM1z0/th04) -On supported capture hosts, a missing or incompatible PTY binary can be fixed -by reinstalling REA for the current platform, architecture, and Node.js version -with optional dependencies enabled. Use separate scenario and output files; -`--json` produces the JSON consumed by the comparison command. - -ASAR inventory verifies Electron integrity metadata for both archive entries -and `.asar.unpacked` companion files. Integrity failures identify the logical -path, declared and calculated SHA-256 values, and whether the entry was -unpacked; REA does not silently accept the mismatched artifact. If a supplied -ASAR declares unpacked companion bytes that are absent from the local artifact -set, REA keeps that occurrence as `unavailable` and continues analyzing the -embedded JavaScript instead of treating the missing native/resource bytes as -verified or absent. - -## Security model - -Analysis runs locally. REA communicates with Hopper and Ghidra through authenticated private local sockets and with IDA through its configured local MCP registration. Your agent or model provider has its own data policy. - -Runtime requests act on the declared target and lifecycle. Analysis tools and launched targets run with your user permissions, and native UI capture still depends on macOS Accessibility and Screen Recording access. Static JavaScript analysis does not execute extracted modules; use direct browser, Electron, or process capture when runtime behavior is needed. - -Windows Ghidra P0 automatically uses native Job Objects, protected private-runtime DACLs, and handle-based admission. Its experimental scope is native x86-64 PE applications on local NTFS. Report vulnerabilities through the private process in [SECURITY.md](SECURITY.md). +If you've used REA on something interesting, we'd love to see it. Share your +case in an [issue](https://github.com/morluto/rea/issues) or a +[pull request](https://github.com/morluto/rea/pulls), including the target, +your question, how REA helped, and what you found. ## FAQ -
-Does Hopper need to be running before I start REA? - -No. REA starts Hopper when an operation needs it. An already-running Hopper application is also supported. - -
- -
-Why did Hopper appear in front of my other windows? - -Hopper's launcher internally activates the application. REA requests background startup, but macOS and Hopper may still bring a window or dialog forward. See [Current Hopper provider](#current-hopper-provider). - -
- -
-Does REA include Hopper? - -No. Setup can install Hopper for you, but Hopper remains separate software with its own license. REA supplies the CLI, MCP server, and workflows that make it usable by agents. - -
- -
-Does REA install or include Ghidra or Java? - -No. REA connects to an existing Ghidra installation. Run setup after providing the Ghidra and Java paths shown in the [Ghidra section](#ghidra-analysis-provider). - -
- -
-Does REA upload the app? - -REA has no hosted analysis service. Current providers analyze artifacts and capture behavior locally. Your agent or model provider may have its own data policy, so review that separately. - -
- -
-Can REA recover the original source code? - -No decompiler can guarantee the original source. REA gives an agent pseudocode, assembly, symbols, strings, metadata, and relationships that it can use to explain or compatibly recreate observed behavior. - -
-
Which agents can use REA? -Any agent that can run a local MCP server can use the manual configuration. Setup offers the supported integrations listed in [Installation and setup](docs/installation.md#supported-agents); existing REA registrations are selected by default, and other detected agents require selection. +Any agent that supports local MCP servers. Setup configures the +[supported agents](docs/installation.md#supported-agents); other clients can use +[manual MCP registration](docs/installation.md#mcp-registry).
-## Development +
+Do I need Hopper, Ghidra or IDA? -See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution checks, and [docs/testing.md](docs/testing.md) for test scopes and real-tool verification. `npm run docs:check` checks committed generated documents; `npm run docs:generate` regenerates them. +Deep native analysis uses one of them. Static JavaScript and .NET inspection +work without a native analysis engine. Setup can install Hopper after approval; +Ghidra and IDA use your existing installations. See [provider setup](docs/installation.md#hopper). -`npm run verify:agent` evaluates native, JavaScript, managed, and browser investigation tasks through a real local Codex CLI. Its report covers tool selection, repeated calls, token use, completion quality, and handling of permissions and unknowns. +
-`npm run evidence:generate` regenerates the managed conformance manifest and Evidence completion ledger from live verification results. `npm run evidence:check` reruns verification and checks for drift. Unsupported claims remain explicit and do not count as passes. See [testing](docs/testing.md) for verification commands and process-cleanup checks. +
+Do I need to start Hopper first? + +REA starts Hopper when an operation needs it. On macOS, a first-run dialog may +ask you to choose demo mode or activate your license. See +[Hopper startup and troubleshooting](docs/installation.md#launcher-paths-and-troubleshooting). + +
+ +
+What does installing the skill from skills.sh do? + +The skill supplies investigation instructions for your agent. Use `rea setup` +to register REA's MCP server and install the matching instructions, then restart +your agent. See [skill-only installation](docs/installation.md#skill-only-installation). + +
+ +
+What code does REA return? + +Native analysis returns pseudocode and assembly. JavaScript/Electron analysis +recovers modules and their relationships. Your agent uses these findings to +write and test an implementation; the [showcases](#showcases) give worked examples. + +
+ +
+Does REA upload my app? + +REA analyzes targets locally. Your agent receives the tool results, and its +model provider has its own data policy. + +
+ +
+What should I do if I hit a bug? + +Update first; a recent release may already fix it. + +For an npm-installed CLI: + +```bash +rea update +``` + +For agent setup through `npx`: + +```bash +npx rea-agents@latest setup +``` + +If you're using an agent, complete the [setup refresh](#update-rea) and restart +it. Retry the same task. If the problem persists, [open an issue](https://github.com/morluto/rea/issues) +with your REA version, target type, steps to reproduce and error output. + +
+ +## Documentation + +Start with the website's [worked guides](https://morluto.github.io/rea/guides/). +For exact options, prerequisites and result contracts: + +- [Installation and setup](docs/installation.md): agent registration, provider configuration, updates and uninstall. +- [Readiness and troubleshooting](docs/installation.md#check-readiness-for-your-task): diagnose one agent or analysis engine. +- [CLI and Evidence](docs/cli.md): commands, provider selection, snapshots, import/export and exit statuses. +- [MCP contracts](docs/mcp-contracts.md) and [agent prompts](docs/mcp-prompts.md): tool results, sessions and guided investigations. +- [Tool catalog](docs/product-catalog.json): generated inventory of tools, providers and CLI commands. +- [Roadmap](docs/roadmap.md): planned work and capability trackers. + +Report vulnerabilities through [SECURITY.md](SECURITY.md). + +## Contributing + +We'd love your help with REA! [Open an issue](https://github.com/morluto/rea/issues) to +report a bug or suggest a feature, or [send a pull request](https://github.com/morluto/rea/pulls) +to improve the code or docs. + +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and checks, +[testing](docs/testing.md) for verification lanes, and the +[architecture map](docs/architecture.mermaid) for the project structure. ## Project links -[npm](https://www.npmjs.com/package/rea-agents) · [Issues](https://github.com/morluto/rea/issues) · [Security](SECURITY.md) · [Contributing](CONTRIBUTING.md) · [Hopper](https://www.hopperapp.com/) · [Ghidra](https://github.com/NationalSecurityAgency/ghidra) +[Website](https://morluto.github.io/rea/) · [npm](https://www.npmjs.com/package/rea-agents) · [skills.sh](https://skills.sh/morluto/rea/reverse-engineer-anything) · [Issues](https://github.com/morluto/rea/issues) · [Security](SECURITY.md) ## License diff --git a/README_ar.md b/README_ar.md index 6ad9ce544..45fa442d8 100644 --- a/README_ar.md +++ b/README_ar.md @@ -138,7 +138,7 @@ rea setup ### المتطلبات - macOS 12 أو أحدث -- Ubuntu 24.04+ أو Fedora 41+ أو Arch Linux بنواة 64 بت +- Ubuntu 24.04+ أو Fedora 41+ أو Arch Linux بنواة 64 بت أو CachyOS - Node.js 22.x (>=22.19) أو 24.x (>=24.11) أو 26+ وnpm يتطلب تحليل الملفات التنفيذية الأصلية Hopper أو Ghidra. Hopper برنامج منفصل، وللوضع التجريبي قيود يحددها المورّد، لكن الترخيص المدفوع ليس إلزاميًا. @@ -153,7 +153,7 @@ rea setup يشخّص `npx -y rea-agents@latest doctor` الجهاز والاعتماديات وأدوات التحليل وإعدادات الوكيل من دون تغييرها. أضف `--json` للحصول على تشخيص منظم. -مسار Hopper الافتراضي على Linux هو `/opt/hopper/bin/Hopper`. استخدم `HOPPER_LAUNCHER_PATH` للمسارات الأخرى. إذا كان الملف موجودًا لكن محرك التحليل لا يظهر، فشغّل `ldd /opt/hopper/bin/Hopper | grep 'not found'` للتحقق من المكتبات الناقصة. راجع [دليل Hopper](docs/installation.md#hopper). +على Linux، يفضّل REA الملف التنفيذي `/opt/hopper/bin/Hopper`، وإذا لم يكن متاحًا يتحقق تلقائيًا من `~/.local/share/rea/hopper/bin/Hopper`. استخدم `HOPPER_LAUNCHER_PATH` للمسارات الأخرى. إذا كان الملف موجودًا لكن محرك التحليل لا يظهر، فشغّل `ldd /absolute/path/to/Hopper | grep 'not found'` على مسار Hopper الفعلي للتحقق من المكتبات الناقصة. راجع [دليل Hopper](docs/installation.md#hopper). ### التحديث والإزالة @@ -222,7 +222,7 @@ rea setup | PE/CLI المُدار | 7 | هوية تجميعات .NET وبياناتها الوصفية وتعليمات CIL والاعتماديات الأصلية واستيراد إعادة البناء ومقارنة الإصدارات | | البرامج الثابتة | 2 | فحص مناطق البرامج الثابتة على Linux واستخراجها الصريح | | Android APK | 5 | بيانات الحزمة وmanifest والبحث عن الأصناف وقوائم الأعضاء وفك ترجمة الأساليب والمراجع الثابتة | -| مراقبة المتصفح | 11 | بنية الصفحات وبيانات الشبكة والبرامج النصية وخرائط المصدر واكتشاف WebMCP والصور ومقارنة الالتقاط | +| مراقبة المتصفح | 12 | بنية الصفحات وبيانات الشبكة والبرامج النصية وخرائط المصدر واكتشاف WebMCP والصور ومقارنة الالتقاط | | تحليل Electron | 5 | مراقبة العارضين وخريطة التطبيق الثابتة والمواءمة بين نتائج الثبات والتشغيل | | وقت تشغيل JavaScript | 2 | اكتشاف أهداف Node/Electron Inspector ومواضع البرامج وأحداث سياقات التنفيذ | | سير عمل التطبيقات | 13 | تصدير النصوص البرمجية الملتقطة، تتبع الميزات عبر الطبقات ومقارنة الإصدارات ومواءمة المصدر التاريخي ومقارنة أشكال القيم المعادة والتحقق من إعادة الإنشاء | @@ -243,7 +243,7 @@ rea setup "mcpServers": { "rea": { "command": "npx", - "args": ["-y", "rea-agents@4.1.0", "mcp"] + "args": ["-y", "rea-agents@5.0.0", "mcp"] } } } @@ -313,7 +313,7 @@ npx -y rea-agents@latest compare /absolute/path/to/left-evidence.json /absolute/
هل يعمل REA على Linux أو Windows؟ -يدعم REA نظام macOS 12+ وUbuntu 24.04+ وFedora 41+ وArch Linux بنواة 64 بت. يعمل تحليل Ghidra للقراءة فقط على Linux x64 وmacOS x64/arm64 مع Ghidra 12.1.x وJDK الكامل الذي يعلنه التثبيت (JDK 21 أو أحدث دون حد أعلى في إصدارات 12.1 الحالية)، إضافة إلى حد Windows x64 P0 التجريبي لتطبيقات PE الأصلية x86-64 على NTFS محلي؛ راجع [دليل Windows Ghidra P0](docs/windows-ghidra-p0.md) للنطاق الموثق. +يدعم REA نظام macOS 12+ وUbuntu 24.04+ وFedora 41+ وArch Linux بنواة 64 بت وCachyOS. يعمل تحليل Ghidra للقراءة فقط على Linux x64 وmacOS x64/arm64 مع Ghidra 12.1.x وJDK الكامل الذي يعلنه التثبيت (JDK 21 أو أحدث دون حد أعلى في إصدارات 12.1 الحالية)، إضافة إلى حد Windows x64 P0 التجريبي لتطبيقات PE الأصلية x86-64 على NTFS محلي؛ راجع [دليل Windows Ghidra P0](docs/windows-ghidra-p0.md) للنطاق الموثق.
diff --git a/README_ja.md b/README_ja.md index 13db556e7..e64c367ae 100644 --- a/README_ja.md +++ b/README_ja.md @@ -4,9 +4,9 @@ # REA:あらゆるものをリバースエンジニアリング -### エージェントでアプリの動作からネイティブバイナリまで調べる。 +### バイナリ、アプリ、実行時の動作をひとつの MCP でリバースエンジニアリング。 -**気になる機能を見つけ、仕組みを理解し、自分の形で実装する。** +**気に入った機能を見つけたら、その仕組みをバイナリのレベルまで理解する。** [![npm version](https://img.shields.io/npm/v/rea-agents?style=flat-square&color=cb3837)](https://www.npmjs.com/package/rea-agents) [![CI](https://img.shields.io/github/actions/workflow/status/morluto/rea/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/morluto/rea/actions/workflows/ci.yml) @@ -45,11 +45,11 @@ --- -アプリの機能を自分のプロダクトにも取り入れたいときは、REA を使ってエージェントに調査を依頼できます。ソースコードがなくても、アプリを調べ、仕組みと根拠を示し、あなたのプロジェクト向けに同様の機能を実装できます。 +自分のプロダクトにも欲しい機能を他のアプリで見つけたら、REA を使ってエージェントに調査を依頼できます。エージェントはソースコードがなくてもアプリを調べ、機能の仕組みを説明して根拠を示し、あなたのプロジェクト向けの実装まで進められます。 -REA はネイティブバイナリ、JavaScript/Electron アプリ、.NET アセンブリ、Web サイトの解析ツールを提供します。エージェントからもターミナルからも使えます。解析はローカルで実行され、結果には根拠と制限が含まれます。 +REA は、ネイティブバイナリ、JavaScript/Electron アプリ、.NET アセンブリ、Web サイトを調べるためのツールをエージェントに提供します。同じツールはターミナルからも使えます。解析はローカルで実行され、結果には各結論の根拠と制限が含まれます。 -Setup はエージェントを設定し、既存の Hopper または Ghidra に接続します。承認後に Hopper をインストールすることもできます。 +セットアップは REA をエージェントに登録し、対応するワークフロー手順もインストールします。ネイティブ解析には既存の Hopper または Ghidra を使えます。承認すれば、セットアップで Hopper をインストールすることもできます。静的な JavaScript 解析にはどちらのエンジンも不要です。 ## エージェントに頼むだけ @@ -59,7 +59,7 @@ Setup はエージェントを設定し、既存の Hopper または Ghidra に メモアプリの検索機能を調べて根拠を示し、私のプロジェクト向けに同様の機能を実装してください。 ``` -メモアプリを調べたいアプリに置き換えるか、まず概要を依頼してください。 +「メモアプリ」を調べたいアプリに置き換えてください。まずは概要の調査を依頼することもできます。 ## バイナリから動作へ @@ -67,56 +67,73 @@ Setup はエージェントを設定し、既存の Hopper または Ghidra に | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | ネイティブアプリや実行ファイルから、プロシージャ、疑似コード、アセンブリ、文字列、シンボル、セグメント、メタデータを復元します。 | 呼び出し元、呼び出し先、クロスリファレンス、コールグラフをたどり、機能やアルゴリズムの実際の動作を説明できる状態にします。 | エージェントが得た知見を、あなたの技術スタック、画面、要件に合うプロダクト機能へ変えます。 | -REA は調査をバイナリ上の根拠に結び付けます。元のソースコードを復元したり、アプリ全体を自動複製したりするとは主張しません。 +REA は結論に至った根拠を示します。元のソースコードを復元できる、あるいはアプリ全体を自動的に複製できるとは主張しません。 ## REA を選ぶ理由 -| | | -| -------------------------- | ---------------------------------------------------------------------------------------------------- | -| **エージェント向け** | アプリの動作について質問し、推測ではなくエージェントに調査させることができます。 | -| **CLI と MCP** | ターミナルとエージェントから同じリバースエンジニアリング機能を使えます。 | -| **ガイド付きセットアップ** | エージェントの設定、既存の解析ツールへの接続、承認後の Hopper インストールに対応します。 | -| **洞察からコードへ** | 機能を理解したら、同じコーディングセッションで自分のプロダクト向けに実装できます。 | -| **ローカルで解析** | 解析は対応するローカルホストで実行され、REA がバイナリをホスト型解析サービスへ送ることはありません。 | -| **コンテキストを維持** | 質問ごとに解析を最初からやり直さず、複数のアプリを続けて調査できます。 | +| | | +| -------------------------- | -------------------------------------------------------------------------------------------------------- | +| **エージェント向け** | アプリの動作を質問すると、エージェントが推測ではなく実際に調べて答えます。 | +| **CLI と MCP** | 同じリバースエンジニアリング機能を、ターミナルからもエージェントからも使えます。 | +| **ガイド付きセットアップ** | エージェントの設定、既存の解析ツールへの接続、承認したうえでの Hopper のインストールに対応します。 | +| **洞察からコードへ** | 機能を理解したら、同じコーディングセッションで自分のプロダクト向けに実装できます。 | +| **ローカルで解析** | 解析は対応するローカルホストで実行されます。REA がアプリをホスト型の解析サービスへ送ることはありません。 | +| **コンテキストを維持** | 質問のたびに解析をやり直すことなく、複数のアプリを続けて調査できます。 | ## クイックスタート ### セットアップを実行(推奨) -REA をエージェントで使うための設定を行います: +REA をエージェントで使えるように設定します: ```bash npx rea-agents setup ``` -Setup は最初に連携するエージェントを複数選択できるようにします。既存の REA 登録は初期選択されますが、検出されただけのクライアントは自動選択されず、未設定のクライアントも選べます。具体的なパスと変更内容を確認してから承認してください。選択したエージェントには通常 REA のワークフローをインストールします。Hopper は別の任意操作で、個別の承認が必要です。既存の Ghidra のパスも登録できます。 +セットアップでは、まず REA を使うエージェントを選び(複数選択可)、具体的なパスと変更内容を確認してから承認します。既存の REA 登録は最初から選択されています。新しく検出されたエージェントは選択肢に表示されますが、検出されただけでは選択されません。選択したエージェントには、MCP 接続と REA のガイド付きワークフローが追加されます。Hopper のインストールは別の任意項目で、個別に承認が必要です。既存の Ghidra のインストール先を登録することもできます。 -変更は事前に表示され、既存の設定はバックアップされます。要件と追加オプションは[インストールガイド](docs/installation.md)を参照してください。 +セットアップは変更を適用する前に内容を表示し、既存の設定をバックアップします。要件とセットアップのオプションは[インストールガイド](docs/installation.md)を参照してください。 -### エージェントで使う +### エージェントで使う(推奨) -設定後にエージェントを再起動し、調べたいアプリや機能を説明します。REA は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応します。既存の REA 登録は初期選択され、それ以外の検出済みクライアントは選択が必要です。その他のエージェントは下記の MCP 設定を使えます。 +セットアップ後にエージェントを再起動し、理解したいアプリや機能を[説明してください](#エージェントに頼むだけ)。Hopper はデモモードでも動作します。初回起動の画面が表示されたら、デモを選ぶか既存のライセンスを入力してください。 -Hopper はデモモードで使えます。初回起動の画面ではデモを選択するか、既存のライセンスを入力してください。 +REA は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応しています。セットアップでは既存の REA 登録が最初から選択され、それ以外の検出済みエージェントは選択するまで対象になりません。その他のエージェントは[手動の MCP 設定](#他のコーディングエージェントで使う)を使えます。 ### AI コーディングアシスタント向けスキル(任意) -AI コーディングアシスタントにスキルを追加すると、より豊富なコンテキストが得られます: +AI コーディングアシスタントにスキルを追加すると、より豊富なコンテキストを利用できます: ```bash npx skills add morluto/rea --skill reverse-engineer-anything ``` -このスキルは REA の調査ワークフローを提供します。上記の setup を実行して REA をエージェントに接続し、解析ツールを設定してください。Setup は既定でバージョンに合ったスキルをインストールします。このコマンドではリポジトリ版のスキルをインストールします。 +このスキルは REA の調査ワークフローを提供します。REA をエージェントに接続して解析ツールを設定するには、上記のセットアップを実行してください。セットアップは既定でバージョンの一致したスキルをインストールします。このコマンドはリポジトリ版のスキルをインストールします。 -展開済みの JavaScript/Electron アプリのツリーや ASAR であれば、MCP の設定もネイティブエンジンも不要です: +### ターミナルで最初の結果を得る + +展開済みの JavaScript/Electron アプリのディレクトリまたは ASAR に対して、次を実行します: ```bash npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json ``` -パスを調査対象に置き換えてください(Windows では `"D:/apps/example"` のようなパス)。この静的ワークフローではそのようなパスも扱えます。ネイティブ解析には専用のエンジン設定が必要です。 +パスは調査対象に置き換えてください(Windows では `"D:/apps/example"` など)。MCP の設定、Hopper、Ghidra を必要とせず、アプリを実行することもなく、Evidence、復元したグラフ、制限、未解決事項をその場で返します。ネイティブアプリの場合は、先にエンジンを設定してから、そのアプリのパスを指定して `analyze` を使います。`doctor` は問題を診断したいときに使うもので、解析のたびに実行する必要はありません。 + +### 作業に合わせて準備状況を確認する + +オプションなしの `rea doctor` は連携全体の監査です。検出したすべてのエージェント登録、インストール済みのスキル、すべての任意の解析エンジンを確認するため、今の作業には支障がなくても `healthy: false` を報告することがあります。作業に合わせて確認範囲を指定してください: + +| 作業 | 準備状況の確認 | +| ------------------------------------------ | ---------------------------------------------------------------------------------------------- | +| 静的な JavaScript/Electron 解析 | 不要です。`analyze-javascript-application` を直接実行します。 | +| 特定の解析エンジンのトラブルシューティング | `rea doctor --provider ghidra --json`(または `hopper`、`ida`) | +| 特定のエージェントの MCP 登録を確認 | `rea doctor --client codex --json`([クライアント ID](docs/installation.md#supported-agents)) | +| インストール済みのスキルを確認 | `rea doctor --skill --json` | + +範囲を指定したレポートでは、`healthy` と終了ステータスを決めるのは `scope_checks` だけです。それ以外の項目は `informational_checks` に表示され、今の作業のために修正する必要はありません。たとえば、使っていないエンジンが見つからなくても対処は不要です。 + +ネイティブ対象に対応するエンジンが複数インストールされている場合、REA は自動ではどれも選びません。対象を開くと `code: "capability_unavailable"` と `details.selection_reason: "ambiguous"` で失敗し、`details.candidate_ids` に候補が示されます。CLI の `--provider` か `open_binary` の `provider_id` で一度選ぶか、常用する設定として `REA_ANALYSIS_PROVIDER` を指定してください。詳しくは[英語版の解説](README.md#choosing-a-deep-analysis-provider)を参照してください。 ### rea コマンドをインストール @@ -126,9 +143,9 @@ npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app -- curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash ``` -Node.js と npm を先にインストールしてください。ターミナルで実行すると、インストーラーは `rea` を追加してセットアップを開始します。 +Node.js と npm を先にインストールしておいてください。ターミナルで実行すると、インストーラーは `rea` を追加してセットアップを開始します。 -npm でインストールし、セットアップを実行することもできます: +npm でインストールしてから、セットアップを実行することもできます: ```bash npm install --global rea-agents @@ -137,38 +154,56 @@ rea setup ### 要件 +静的な JavaScript 解析に必要なのは Node.js と npm だけです。ホストと外部ツールの前提条件は使うワークフローによって異なり、ネイティブ解析プロバイダーの対応プラットフォームは各ガイドで説明しています。 + - macOS 12 以降 -- Ubuntu 24.04+、Fedora 41+、または 64 ビット Arch Linux -- Node.js 22.x (>=22.19)、24.x (>=24.11)、または 26+ と npm +- Ubuntu 24.04+、Fedora 41+、64 ビット Arch Linux、または CachyOS +- Node.js 22.x (>=22.19)、24.x (>=24.11)、または 26+ +- npm(特定のバージョンは不要で、REA が npm をインストールすることもありません) -ネイティブバイナリ解析には Hopper または Ghidra が必要です。Hopper は別製品です。デモにはベンダー所定の制限がありますが、有料ライセンスは必須ではありません。 +ネイティブバイナリの詳細な解析には Hopper、Ghidra、IDA Pro のいずれかが必要です。Hopper は独自のライセンスを持つ別製品で、デモ版でもベンダーが定める制限の範囲で解析できます。Ghidra と IDA は、利用者が用意したものを使います。 -Ghidra は Linux x64 と macOS x64/arm64 に対応します。Ghidra 12.1.x と、そのインストールが宣言する完全な 64 ビット JDK(`application.java.min` から `application.java.max` まで)を別途インストールし、REA で使うように設定してください。現行の 12.1 リリースは JDK 21 以降を要求し、上限は設定しません。ブリッジは Ghidra 12.1.4 と JDK 21 で検証しています。macOS ではホストのアーキテクチャに合うネイティブデコンパイラーも必要です。 +Ghidra は Linux x64 と macOS x64/arm64 に対応しています。Ghidra 12.1.x と、そのインストールが宣言する 64 ビットの完全な JDK(`application.java.min` から `application.java.max` まで)を別途インストールし、REA で使うように設定してください。現行の 12.1 リリースは JDK 21 以降を必要とし、上限はありません。ブリッジは Ghidra 12.1.4 と JDK 21 で検証しています。macOS では、ホストのアーキテクチャに合うネイティブデコンパイラーも必要です。 -Setup はインストールを確認し、パスを保存できます。Ghidra、Java、Node.js、npm、Homebrew のインストールや更新は行いません。 +```bash +export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC +export JAVA_HOME=/absolute/path/to/jdk-21 # java と javac が PATH から解決できる場合は省略可 +rea doctor --provider ghidra --json +rea setup +``` -リポジトリの main と npm 4.1.0 には、ローカル NTFS 上のネイティブ x86-64 PE アプリケーション(非マネージド、非 DLL 向け)を対象とする実験的な Windows x64 Ghidra P0 対応が含まれています。Job Object、プライベート DACL、パス許可の各制御が同梱されています。古い npm パッケージに期待する前に[リリース境界](docs/installation.md#released-package-and-main)を確認してください。前提条件と検証済みの範囲は [Windows Ghidra P0](docs/windows-ghidra-p0.md) を参照してください。 +セットアップはこれらのインストールを確認し、承認後にパスを保存します。Ghidra、Java、Node.js、npm、Homebrew のインストールや更新は行いません。 + +IDA Pro は、すでに動作している [mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp) の MCP 登録を再利用します。開いている GUI の対象を読み取り専用で解析するか(`attached`、既定)、データベースのスーパーバイザーで指定したバイナリをヘッドレスで開いて解析します(`headless`)。REA が attached の GUI データベースを保存したり閉じたりすることはありません。最初の実環境での検証は、Windows の GUI と Windows x64 のヘッドレス IDA 9.3 が対象です。設定方法と対応範囲は [IDA プロバイダーガイド](docs/ida-provider.md)を参照してください。 + +```bash +export REA_IDA_MCP_CONFIG=/absolute/path/to/ida-mcp.json +rea function /absolute/path/to/program main --provider ida --json +``` + +リポジトリの main と npm 4.1.0 には、ローカル NTFS 上のネイティブ x86-64 PE アプリケーション(マネージドコードと DLL を除く)を対象とする、実験的な Windows x64 Ghidra P0 対応が含まれています。Job Object、プライベート DACL、パスの受け入れ検査の各制御も同梱されています。古い npm パッケージでこの機能を使えるか確認するには[リリース境界](docs/installation.md#released-package-and-main)を、前提条件と検証済みの範囲は [Windows Ghidra P0](docs/windows-ghidra-p0.md) を参照してください。 ### トラブルシューティング -`npx -y rea-agents@latest doctor` はホスト、依存関係、解析ツール、エージェント設定を変更せずに確認します。構造化された診断には `--json` を追加してください。 +`npx -y rea-agents@latest doctor` は、ホスト、依存関係、解析ツール、エージェント設定を変更せずに確認します。構造化された診断結果が必要な場合は `--json` を追加してください。 -Linux の既定ランチャーは `/opt/hopper/bin/Hopper` です。別の場所には `HOPPER_LAUNCHER_PATH` を設定します。ファイルがあるのに解析エンジンが見つからない場合は、`ldd /opt/hopper/bin/Hopper | grep 'not found'` で不足ライブラリを確認してください。詳しくは [Hopper ガイド](docs/installation.md#hopper)を参照してください。 +Linux では実行可能な `/opt/hopper/bin/Hopper` を優先し、利用できない場合は `~/.local/share/rea/hopper/bin/Hopper` を自動的に確認します。それ以外の場所にインストールした場合は `HOPPER_LAUNCHER_PATH` を設定してください。ファイルがあるのに解析エンジンが見つからないと報告される場合は、実際の Hopper のパスに対して `ldd /absolute/path/to/Hopper | grep 'not found'` を実行し、不足しているライブラリを確認してください。詳しくは [Hopper ガイド](docs/installation.md#hopper)を参照してください。 ### 更新とアンインストール - `rea update` は現在の REA インストールを更新します。 -- `rea uninstall` は REA が管理するエージェント登録とワークフローファイルを削除します。Hopper は残ります。 -- `rea uninstall --purge-data` は REA のキャッシュと状態も削除します。それらを削除したい場合にだけ使ってください。 +- `rea uninstall` は REA が管理するエージェント登録とスキルだけを削除します。Hopper、Node.js、Evidence ファイル、キャプチャ、無関係なスキル、他の MCP サーバーは残ります。 +- `rea uninstall --purge-data` は `~/.rea/cache` と `~/.rea/state` も削除します。それらを消したい場合にだけ使ってください。 ## 現在の対応状況 リポジトリの現在の機能とプラットフォーム要件は[英語の対応状況ガイド](README.md#current-status)で説明しています。main は [npm リリース](docs/installation.md#released-package-and-main)より先行する場合があります。 -- Ghidra は Linux x64、macOS x64/arm64、実験的な Windows x64 P0 境界で 25 件の読み取り専用操作を提供します。Linux/macOS ではアトミックなセッション内関数注釈にも対応します。Windows P0 は読み取り専用であり、Ghidra に GUI 操作はありません。 -- 静的な Android 検査には持ち込みの JADX/Java が別途必要です。実プロバイダー検証は Linux を対象とします。詳しくは[Android 解析](docs/android-analysis.md)を参照してください。 -- ブラウザー、Electron、プロセスの各リクエストは対象・操作・ライフサイクルを直接指定し、ホストの実際のアクセスに依存します。REA 側の個別の許可付与は不要です。Setup による設定書き込みと Hopper のインストールは、引き続き正確な計画の承認が必要です。 -- `rea capabilities` はバイナリセッション操作の説明であり、すべてのアプリ/ランタイムツールの目録ではありません。接続済み MCP ツール一覧と関連ガイドを参照してください。 +- Ghidra は Linux x64、macOS x64/arm64、実験的な Windows x64 P0 境界で 25 種類の読み取り専用操作を提供します。Linux と macOS では、セッション内での関数名とエントリーコメントのアトミックな編集にも対応します。Windows P0 には変更の権限がなく、Ghidra には REA から操作できる GUI 機能はありません。 +- IDA アダプターは、ドキュメントに記載された読み取り専用の関数・文字列操作を提供します。 +- 静的な Android APK の検査は Linux と macOS に対応し、別途用意したヘッドレス JADX と完全な JDK が必要です。現在のメタデータブリッジは macOS arm64 で検証しています。詳しくは [Android 解析](docs/android-analysis.md)を参照してください。 +- ブラウザー、Electron、プロセスの各リクエストは対象、操作、ライフサイクルを直接指定し、ホストで実際に許可されたアクセスの範囲で動作します。REA 側で別途権限を付与する必要はありません。セットアップによる設定の書き込みと Hopper のインストールには、引き続き具体的な計画の承認が必要です。 +- `rea capabilities` と `rea providers` はバイナリセッションのプロバイダーと補助操作を説明するもので、ブラウザー、Android、アプリのすべてのワークフローの一覧ではありません。MCP の機能全体は、接続中の MCP ツール一覧と `binary_session` が示すツールの利用可否で確認してください。 ## ひとつのプロンプトで調査を完結 @@ -181,49 +216,54 @@ TypeScript と SQLite を使って私のプロジェクト向けに実装して | ---: | ------------------------------ | ---------------------------------------------------------------- | | 1 | バイナリを開いて識別 | `open_binary`, `binary_overview` | | 2 | オフライン検索の手掛かりを探す | `search_strings`, `search_procedures`, `list_names` | -| 3 | 手掛かりと実行コードを接続 | `find_xrefs_to_name`, `xrefs`, `procedure_callers` | -| 4 | 制御フローを復元 | `get_call_graph`, `procedure_callees`, `procedure_info` | +| 3 | 手掛かりを実行コードに結び付け | `find_xrefs_to_name`, `xrefs`, `procedure_callers` | +| 4 | 関連する制御フローを復元 | `get_call_graph`, `procedure_callees`, `procedure_info` | | 5 | 関連する処理を逆コンパイル | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` | -| 6 | プロジェクトに機能を実装する | 技術スタック、プロダクト、要件に合わせたコード | +| 6 | プロジェクトに機能を実装 | 技術スタック、プロダクト、要件に合わせたコード | -REA は手順 1〜5 のバイナリ解析を処理し、手順 6 はエージェントの通常の編集・テストツールが行います。 +手順 1〜5 のアプリ解析は REA が担当します。手順 6 は、アプリについて得た知見をもとに、エージェントが通常の編集ツールとテストツールで行います。 ## エージェントにできること -- ソースコードがない機能の仕組みを説明する。 -- アプリの認証、保存、更新、ネットワークフローを復元する。 -- 非公開の形式やインターフェースを文書化できる構造を回収する。 -- 文字列やシンボルから疑わしい動作の実装コードまで追跡する。 -- 同じセッションで 2 バージョンを切り替え、実装経路を比較する。 -- 気になる機能を調査し、自分のプロダクトに合わせた形で実装する。 -- 復元した動作をプロダクト機能、テスト、移行ノート、移植、相互運用できる代替実装へ変換する。 -- Swift / Objective-C のメタデータを解析する。 -- Hopper に名前、コメント、ブックマークを残し、人間とエージェントの調査を共有する。 +- 気に入った機能を調査し、自分のプロダクトに合わせた形で実装する。 +- ソースコードが手に入らない機能の仕組みを説明する。 +- アプリの認証、保存、更新、ネットワーク処理の流れを復元する。 +- 文書化されていない形式やインターフェースについて、文書化に必要な構造を復元する。 +- 文字列やシンボルから、疑わしい動作を実装しているコードまでたどる。 +- 復元した動作を、プロダクト機能、テスト、移行メモ、移植版、相互運用できる代替実装に変える。 +- マングルされたシンボルをひとつずつ手作業で読み解かなくても、Swift と Objective-C のメタデータを解析する。 +- Hopper に名前、コメント、ブックマークを残し、人とエージェントの解析を互いに活かす。 ## 調査ツールカタログ -| ツール分類 | 数 | 用途 | -| -------------------- | --: | -------------------------------------------------------------------------------------------------------------------------------------------- | -| ネイティブ検査 | 41 | 関数、疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照、注釈、バイト読み取り、ファイルオフセット | -| 調査ワークフロー | 14 | アプリ概要、関数ドシエ、ネイティブ API とディスパッチ、一括逆コンパイル、機能トレース、コールパス、コールグラフ、Swift と Objective-C の検出 | -| macOS ネイティブ | 8 | Hopper を起動せずに扱える Mach-O メタデータ、署名、plist、アーキテクチャ、Swift 名の復元、LLDB による呼び出し観測 | -| 成果物グラフ | 5 | ディレクトリとパッケージの目録、コンパイル済み Interface Builder ファイル、Apple アセットカタログ、抽出 | -| マネージド PE/CLI | 7 | .NET の識別、メタデータ、CIL 命令、ネイティブ依存関係、再構築インポート、ビルド比較 | -| ファームウェア | 2 | Linux ファームウェア領域の検査と明示的な展開 | -| Android APK | 5 | パッケージと manifest の宣言、クラス検索、メンバー一覧、メソッド逆コンパイル、静的参照 | -| ブラウザー観察 | 11 | ページ構成、ネットワークメタデータ、スクリプト、ソースマップ、WebMCP 検出、スクリーンショット、キャプチャ比較 | -| Electron 解析 | 5 | レンダラー観察、静的アプリマッピング、静的/実行時結果の対応付け | -| JavaScript 実行時 | 2 | Node/Electron Inspector 対象の検出、スクリプト位置、実行コンテキストイベント | -| アプリワークフロー | 13 | 取得した Web スクリプトのエクスポート、層をまたぐ機能トレース、ビルド比較、履歴ソース対応、静的戻り値形状の比較、再実装検証 | -| ワークスペースと観察 | 21 | セッション、証跡バンドル、ナビゲーション文脈、プロセス・成果物・関数比較、未解決事項の記録 | +| ツール分類 | 数 | 用途 | +| -------------------- | --: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ネイティブ検査 | 41 | 関数、疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照、注釈、バイト読み取り、ファイルオフセット | +| 調査ワークフロー | 14 | アプリ概要、関数ドシエ、ネイティブ API とディスパッチ、一括逆コンパイル、機能トレース、コールパス、コールグラフ、Swift と Objective-C の検出 | +| macOS ネイティブ | 8 | Hopper を起動せずに扱える Mach-O メタデータ、コード署名、plist、アーキテクチャ、Swift シンボルのデマングル、LLDB による呼び出し観測 | +| 成果物グラフ | 5 | ディレクトリとパッケージの目録、コンパイル済み Interface Builder ファイル、Apple アセットカタログ、抽出 | +| マネージド PE/CLI | 7 | .NET の識別情報、メタデータ、CIL 命令、ネイティブ依存関係、再構築結果のインポート、ビルド比較 | +| ファームウェア | 2 | Linux ファームウェア領域の検査と明示的な展開 | +| Android APK | 5 | パッケージと manifest の宣言、クラス検索、メンバー一覧、メソッドの逆コンパイル、静的な被参照 | +| ブラウザー観察 | 12 | ページ構成、ネットワークメタデータ、スクリプト、ソースマップ、WebMCP の検出、スクリーンショット、キャプチャ比較 | +| Electron 解析 | 5 | レンダラーの観察、静的なアプリのマッピング、静的結果と実行時結果の照合 | +| JavaScript 実行時 | 2 | Node/Electron Inspector の対象検出、スクリプトの位置、実行コンテキストのイベント | +| アプリワークフロー | 13 | 取得した Web スクリプトのエクスポート、Android/Apple の目録の投影、層をまたぐ機能トレース、ビルド比較、過去のソースとの対応付け、静的な戻り値形状の比較、再構築の検証 | +| ワークスペースと観察 | 21 | セッション、Evidence バンドル、ナビゲーションの文脈、プロセス・成果物・関数の比較、未解決事項の記録 | ## ロードマップ -次の作業はネイティブ対象の検証拡大、JavaScript と .NET のバージョン比較、実行時の観察と再実装の検証です。Setup はエージェント連携と Hopper のインストールを選択できます。他の解析ツールのインストールは今後の作業です。[インストールのロードマップ](docs/roadmap.md)と[解析ツールの評価](docs/provider-evaluation.md)を参照してください。 +[現在の対応状況](#現在の対応状況)は提供済みの機能です。今後の作業は次のとおりです。 + +- **現在:** ツール、プロバイダー、セットアップの選択肢、バージョンの変更に合わせてドキュメントを正確に保ち、Hopper と Ghidra で検証するネイティブバイナリのアーキテクチャと間接呼び出しの範囲を広げます。 +- **次:** 機能トレースに静的抽出と実行時の観察を追加し、難読化された .NET アセンブリの比較を改善し、プロセス、プロトコル、ファイルシステム、再接続、バージョン比較の範囲を広げます。 +- **その後:** ブラウザーと Electron のシナリオ操作を増やし、LLDB や Frida などによるネイティブアプリの実行時観察と、他の解析ツールや対象の評価を進めます。 + +セットアップでは、すでにエージェント連携と Hopper のインストールを選べます。その他の解析ツールのインストールへの対応は今後の作業です。[インストールのロードマップ](docs/roadmap.md)と[解析ツールの評価](docs/provider-evaluation.md)を参照してください。 ## 他のコーディングエージェントで使う -Setup は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応します。既存の REA 登録は初期選択され、それ以外の検出済みクライアントは選択が必要です。ローカル MCP サーバーに対応するエージェントは、次の設定でも接続できます。 +セットアップは Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf、Devin、OpenCode、Antigravity、GitHub Copilot CLI、Command Code、VS Code に対応しています。既存の REA 登録は最初から選択され、それ以外の検出済みエージェントは選択するまで対象になりません。ローカル MCP サーバーに対応するエージェントであれば、次の設定でも接続できます。 @@ -232,7 +272,7 @@ Setup は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf "mcpServers": { "rea": { "command": "npx", - "args": ["-y", "rea-agents@4.1.0", "mcp"] + "args": ["-y", "rea-agents@5.0.0", "mcp"] } } } @@ -240,29 +280,33 @@ Setup は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf +常用する登録では、パッケージのバージョンを 1 つに固定してください。`rea setup` はその固定バージョンを維持し、同梱のスキルも同時に更新します。 + ## 仕組み ```mermaid flowchart LR Agent["コーディングエージェント"] --> REA["REA
CLI + MCP"] Terminal["ターミナル"] --> REA - REA --> Hopper["Hopper 解析操作"] + REA --> Hopper["Hopper プロバイダー"] + REA --> Ghidra["Ghidra プロバイダー
インベントリ + 関数解析 + 注釈"] + REA --> Ida["IDA MCP プロバイダー
attached GUI または headless データベース"] Hopper --> App["あなたのアプリ"] - REA --> Ghidra["Ghidra 読み取り専用解析
インベントリ + 関数解析"] Ghidra --> App + Ida --> App ``` -CLI と MCP サーバーは同じ解析処理を使います。ターミナルコマンドは完了後に自分のブリッジセッションを解放し、エージェントセッションは調査中の接続を維持できます。REA セッションを閉じても、ユーザーが利用中の Hopper は終了しません。 +CLI と MCP サーバーは、同じアプリケーションワークフローと Evidence の契約を使います。ターミナルのコマンドは完了すると自身のブリッジセッションを解放し、エージェントのセッションは調査中の対象と Evidence を保持できます。REA のセッションを閉じても、利用中の Hopper アプリは終了しません。 ## CLI -上のエージェントワークフローが、REA を使う最も簡単な方法です。ターミナルから一度だけアプリの概要を調べる場合は、次を実行します。 +上のエージェントワークフローが、REA を使う最も簡単な方法です。ターミナルからアプリの概要を一度だけ調べる場合は、次を実行します。 ```bash npx -y rea-agents@latest analyze /Applications/Notes.app ``` -直接デコンパイルする方法やその他のオプションは、`npx -y rea-agents@latest --help` で確認できます。 +直接の逆コンパイルやその他のオプションは、`npx -y rea-agents@latest --help` で確認できます。 グローバルな `rea` コマンドとしてもインストールできます。 @@ -272,41 +316,49 @@ rea --help rea mcp ``` -REA は Mac の `.app` フォルダーを直接開けます。エージェントがアプリを見つけられない場合は、インストール場所を伝えてください。 +REA は Mac の `.app` フォルダーを直接開けます。エージェントがアプリを名前で見つけられない場合は、インストール場所を伝えてください。 ## Hopper アプリの動作 -REA は必要なときに Hopper を起動します。Hopper のランチャーは内部でアプリをアクティブ化するため、ターゲットを開くと Hopper が前面に出る場合があります。REA はバックグラウンド起動を要求しますが、常に背面に留まる保証はありません。 +REA は操作に必要になった時点で Hopper を起動します。Hopper のランチャーは内部でアプリをアクティブにするため、REA がバックグラウンドでの起動を要求しても、macOS では Hopper のウィンドウやダイアログが前面に出る場合があります。デモやライセンスの画面では操作が必要になることがあります。 -明示的な形式・アーキテクチャ引数により一般的な FAT / ARM 選択ダイアログを避けますが、別の Hopper / macOS ダイアログは人の操作を必要とする場合があります。セッションを閉じるとブリッジとソケットを削除しますが、ユーザーが利用中の Hopper は終了しません。 +Hopper は解析リクエストを一度に 1 つずつ処理します。待機をキャンセルしても、Hopper 内ですでに実行中の処理は止まりません。処理中かどうかはセッションが報告します。セッションを閉じると REA のブリッジと一時ソケットディレクトリを削除しますが、利用中の Hopper アプリは終了しません。後片付けを確認できなかった場合、`close_binary` は `cleanup_incomplete` と対象のリソースを報告します。 ## セキュリティモデル -各セッションはランダムな capability token と現在のユーザーだけが使える Unix ソケットを使用します。Ghidra セッションは隔離された一時プロジェクトも使用し、ユーザー所有の Ghidra プロジェクトを開いたり変更したりしません。これはサンドボックスではなく、同じ OS ユーザー権限で動作する悪意あるプロセスを防御しません。信頼できないバイナリの解析は、現在のユーザー権限で選択されたローカルプロバイダーに委譲されます。脆弱性は [SECURITY.md](SECURITY.md) の非公開手順で報告してください。 +解析はローカルで実行されます。REA は Hopper や Ghidra とは認証付きのプライベートなローカルソケットで、IDA とは設定済みのローカル MCP 登録を通じて通信します。Ghidra セッションは隔離された一時プロジェクトを使い、ユーザーの Ghidra プロジェクトを開いたり変更したりしません。 + +これはサンドボックスではありません。解析ツールと起動した対象は現在のユーザー権限で動作し、同じ OS ユーザー権限で動く悪意のあるプロセスは防げません。Windows Ghidra P0 では、ネイティブの Job Object、保護されたプライベート DACL、ハンドルベースの受け入れ検査を自動的に使います。脆弱性は [SECURITY.md](SECURITY.md) に記載された非公開の手順で報告してください。 ## FAQ -
Hopper を先に起動する必要がありますか? +
Hopper を先に起動しておく必要はありますか? -いいえ。REA が必要時に起動します。Hopper が起動済みでも使えますが、既存の GUI ドキュメントには接続せず、新しい解析ドキュメントを開きます。 +いいえ。REA は必要になった時点で Hopper を起動します。すでに起動している Hopper でも使えますが、既存の GUI ドキュメントには接続せず、新しい解析ドキュメントを開きます。
-
REA に Hopper は含まれますか? +
REA に Hopper は含まれていますか? -含まれません。Setup で Hopper をインストールできますが、Hopper は独自のライセンスを持つ別製品です。REA は CLI、MCP サーバー、エージェント向けワークフローを提供します。 +含まれていません。セットアップで Hopper をインストールできますが、Hopper は独自のライセンスを持つ別製品です。REA は、エージェントが Hopper を使えるようにする CLI、MCP サーバー、ワークフローを提供します。
-
バイナリはアップロードされますか? +
REA は Ghidra や Java をインストールしたり同梱したりしますか? -REA にホスト型解析サービスはありません。ローカル Unix ソケット経由で Hopper を操作します。エージェントやモデル提供者のデータポリシーは別途確認してください。 +いいえ。REA は既存の Ghidra のインストールに接続します。[要件](#要件)にある Ghidra と Java のパスを設定してから、セットアップを実行してください。 + +
+ +
アプリはアップロードされますか? + +REA にホスト型の解析サービスはありません。現在のプロバイダーは、成果物の解析と動作のキャプチャをローカルで行います。エージェントやモデルの提供元には独自のデータポリシーがあるため、別途確認してください。
元のソースコードを復元できますか? -保証できません。REA は疑似コード、アセンブリ、シンボル、文字列、メタデータ、関係を提供し、エージェントが観察した動作を説明または互換再現できるようにします。 +元のソースコードを確実に復元できるデコンパイラーはありません。REA はエージェントに疑似コード、アセンブリ、シンボル、文字列、メタデータ、それらの関係を提供し、観察された動作の説明や互換性のある再現に役立てられるようにします。
diff --git a/README_ko.md b/README_ko.md index cac497b6b..66656e555 100644 --- a/README_ko.md +++ b/README_ko.md @@ -139,7 +139,7 @@ rea setup ### 요구 사항 - macOS 12 이상 -- Ubuntu 24.04+, Fedora 41+ 또는 64비트 Arch Linux +- Ubuntu 24.04+, Fedora 41+, 64비트 Arch Linux 또는 CachyOS - Node.js 22.x (>=22.19), 24.x (>=24.11) 또는 26+와 npm 네이티브 바이너리 분석에는 Hopper 또는 Ghidra가 필요합니다. Hopper는 별도 소프트웨어입니다. 데모에는 공급업체의 제한이 있지만 유료 라이선스가 필수는 아닙니다. @@ -154,7 +154,7 @@ Setup은 설치를 확인하고 경로를 저장할 수 있습니다. Ghidra, Ja `npx -y rea-agents@latest doctor`는 호스트, 의존성, 분석 도구, 에이전트 설정을 변경 없이 확인합니다. 구조화된 진단에는 `--json`을 추가하세요. -Linux의 기본 Hopper 실행 파일은 `/opt/hopper/bin/Hopper`입니다. 다른 위치에는 `HOPPER_LAUNCHER_PATH`를 설정하세요. 파일이 있는데도 분석 엔진이 없다고 하면 `ldd /opt/hopper/bin/Hopper | grep 'not found'`로 누락된 라이브러리를 확인하세요. 자세한 내용은 [Hopper 안내](docs/installation.md#hopper)를 참고하세요. +Linux에서는 실행 가능한 `/opt/hopper/bin/Hopper`를 우선 사용하고, 사용할 수 없으면 `~/.local/share/rea/hopper/bin/Hopper`를 자동으로 확인합니다. 다른 위치에는 `HOPPER_LAUNCHER_PATH`를 설정하세요. 파일이 있는데도 분석 엔진이 없다고 하면 실제 Hopper 경로에 대해 `ldd /absolute/path/to/Hopper | grep 'not found'`를 실행해 누락된 라이브러리를 확인하세요. 자세한 내용은 [Hopper 안내](docs/installation.md#hopper)를 참고하세요. ### 업데이트와 제거 @@ -212,7 +212,7 @@ REA는 1–5단계의 바이너리 분석을 처리합니다. 6단계는 에이 | 관리 PE/CLI | 7 | .NET 식별, 메타데이터, CIL 명령어, 네이티브 종속성, 재구성 가져오기, 빌드 비교 | | 펌웨어 | 2 | Linux 펌웨어 영역 검사 및 명시적 추출 | | Android APK | 5 | 패키지와 manifest 선언, 클래스 검색, 멤버 목록, 메서드 디컴파일, 정적 참조 | -| 브라우저 관찰 | 11 | 페이지 구조, 네트워크 메타데이터, 스크립트, 소스 맵, WebMCP 탐색, 스크린샷, 캡처 비교 | +| 브라우저 관찰 | 12 | 페이지 구조, 네트워크 메타데이터, 스크립트, 소스 맵, WebMCP 탐색, 스크린샷, 캡처 비교 | | Electron 분석 | 5 | 렌더러 관찰, 정적 앱 매핑, 정적·런타임 결과 연결 | | JavaScript 런타임 | 2 | Node/Electron Inspector 대상 탐색, 스크립트 위치, 실행 컨텍스트 이벤트 | | 앱 워크플로 | 13 | 캡처한 웹 스크립트 내보내기, 계층 간 기능 추적, 빌드 비교, 히스토리 소스 매핑, 정적 반환 구조 비교, 재구현 검증 | @@ -233,7 +233,7 @@ Setup은 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin "mcpServers": { "rea": { "command": "npx", - "args": ["-y", "rea-agents@4.1.0", "mcp"] + "args": ["-y", "rea-agents@5.0.0", "mcp"] } } } diff --git a/README_zh.md b/README_zh.md index 1015aad77..29b6052a1 100644 --- a/README_zh.md +++ b/README_zh.md @@ -138,7 +138,7 @@ rea setup ### 系统要求 - macOS 12 或更高版本 -- Ubuntu 24.04+、Fedora 41+ 或 64 位 Arch Linux +- Ubuntu 24.04+、Fedora 41+、64 位 Arch Linux 或 CachyOS - Node.js 22.x (>=22.19)、24.x (>=24.11) 或 26+,以及 npm 原生二进制分析需要 Hopper 或 Ghidra。Hopper 是独立软件;演示模式有厂商规定的限制,不要求购买许可证。 @@ -153,7 +153,7 @@ Setup 可以验证安装并保存路径,不会安装或升级 Ghidra、Java、 运行 `npx -y rea-agents@latest doctor`,检查主机、依赖、分析工具和智能体配置。该命令不会修改文件。添加 `--json` 可获取结构化诊断。 -Linux 上的默认 Hopper 启动器为 `/opt/hopper/bin/Hopper`。其他路径可通过 `HOPPER_LAUNCHER_PATH` 指定。如果文件存在但仍报告缺少分析引擎,运行 `ldd /opt/hopper/bin/Hopper | grep 'not found'` 检查缺少的系统库。安装详情见 [Hopper 指南](docs/installation.md#hopper)。 +Linux 上 REA 会优先使用可执行的 `/opt/hopper/bin/Hopper`;如果不可用,则自动检查 `~/.local/share/rea/hopper/bin/Hopper`。其他路径可通过 `HOPPER_LAUNCHER_PATH` 指定。如果文件存在但仍报告缺少分析引擎,请对实际 Hopper 路径运行 `ldd /absolute/path/to/Hopper | grep 'not found'` 检查缺少的系统库。安装详情见 [Hopper 指南](docs/installation.md#hopper)。 ### 更新与卸载 @@ -211,7 +211,7 @@ REA 负责第 1–5 步中的二进制分析。第 6 步由智能体使用其常 | 托管 PE/CLI | 7 | .NET 程序集身份、元数据、CIL 指令、原生依赖声明、重建导入与构建比较 | | 固件 | 2 | Linux 固件区域检查与显式提取 | | Android APK | 5 | 包与 manifest 声明、类搜索、成员清单、方法反编译与静态引用 | -| 浏览器观察 | 11 | 页面结构、网络元数据、脚本、来源映射、WebMCP 发现、截图与捕获比较 | +| 浏览器观察 | 12 | 页面结构、网络元数据、脚本、来源映射、WebMCP 发现、截图与捕获比较 | | Electron 分析 | 5 | 渲染进程观察、静态应用映射、静态/运行时结果关联 | | JavaScript 运行时 | 2 | Node/Electron Inspector 目标发现、脚本位置与执行上下文事件 | | 应用工作流 | 13 | 捕获的网站脚本导出、跨层功能追踪、构建比较、历史源码映射、静态返回结构比较与重建验证 | @@ -232,7 +232,7 @@ Setup 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsu "mcpServers": { "rea": { "command": "npx", - "args": ["-y", "rea-agents@4.1.0", "mcp"] + "args": ["-y", "rea-agents@5.0.0", "mcp"] } } } diff --git a/bridge/mitmproxy/capture.py b/bridge/mitmproxy/capture.py new file mode 100644 index 000000000..cc24331fe --- /dev/null +++ b/bridge/mitmproxy/capture.py @@ -0,0 +1,210 @@ +"""REA offline adapter using mitmproxy's unchanged native tnetstring decoder. + +No FlowReader migrations, HAR conversion, proxy listener, or target requests. +The containing mitmdump process and its configuration directory are REA-owned. +""" + +import base64 +import hashlib +import json +import math +import re +import resource +from pathlib import Path + +from mitmproxy import ctx, version +from mitmproxy.io import tnetstring + +PROFILE = "12.2.3" +OUTPUT_BYTES = 96 * 1024 * 1024 +CREDENTIALS = {b"authorization", b"proxy-authorization", b"cookie", b"set-cookie"} + + +class CaptureFailure(Exception): + def __init__(self, reason, message, pointer=""): + self.reason, self.pointer = reason, pointer + super().__init__(message) + + +def pointer(parent, key): + return parent + "/" + str(key).replace("~", "~0").replace("/", "~1") + + +class BoundedReader: + """Refuse malicious declared lengths before the upstream parser allocates.""" + def __init__(self, handle, size): + self.handle, self.size = handle, size + + def read(self, count): + if count < 0 or count > self.size - self.handle.tell(): + raise CaptureFailure("format", "Native record declares bytes beyond the retained capture.") + return self.handle.read(count) + + +def project_record(state, secrets): + binaries, numbers, redactions = [], [], [] + byte_secrets = [] + for secret in secrets: + try: + byte_secrets.append(secret.encode("utf-8", errors="strict")) + except UnicodeEncodeError: + # Non-scalar JSON strings have no UTF-8 byte representation. + # Keep them in secrets for the ordinary string projection. + pass + + def redact(text, path, original): + if any(secret in text or secret in original for secret in secrets): + redactions.append({"pointer": path, "reason": "explicit-sensitive-value"}) + return None + return text + + def transport_value(value, path, transport_url): + if not (transport_url or re.fullmatch(r"/(?:backup/)*request/(?:path|authority)", path)): + return value, False + binary = isinstance(value, bytes) + pattern = rb"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@" if binary else r"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@" + replacement = rb"\1" if binary else r"\1" + separator = b"@" if binary else "@" + safe_url = re.sub(pattern, replacement, value, flags=re.I) + if path.endswith("/authority") and separator in safe_url: + safe_url = safe_url.rsplit(separator, 1)[1] + changed = safe_url != value + if changed: + redactions.append({"pointer": path, "reason": "transport-credential"}) + return safe_url, changed + + def validate_omitted(value, path, depth): + before = len(binaries), len(numbers), len(redactions) + visit(value, path, depth) + del binaries[before[0]:] + del numbers[before[1]:] + del redactions[before[2]:] + + def visit(value, path, depth=0, credential=False, transport_url=False): + if depth > 64: + raise CaptureFailure("input-limit", "Native capture exceeds the 64-level evidence nesting budget.", path) + if credential: + validate_omitted(value, path, depth) + redactions.append({"pointer": path, "reason": "transport-credential"}) + if isinstance(value, bytes): + binaries.append({"pointer": path, "representation": "producer-bytes", "state": "redacted", "content_base64": None, "bytes": None, "sha256": None}) + return None + if isinstance(value, bytes): + safe_bytes, credential_url = transport_value(value, path, transport_url) + content_base64 = base64.b64encode(value).decode("ascii") + digest = hashlib.sha256(value).hexdigest() + hidden = any(secret in value or secret in safe_bytes for secret in byte_secrets) or any(secret in content_base64 or secret in digest for secret in secrets) + safe_text = None + try: + safe_text = safe_bytes.decode("utf-8", errors="strict") + except UnicodeDecodeError: + pass + if hidden: + redactions.append({"pointer": path, "reason": "explicit-sensitive-value"}) + exclude = hidden or credential_url + binaries.append({"pointer": path, "representation": "producer-bytes", "state": "redacted" if exclude else "retained", "content_base64": None if exclude else content_base64, "bytes": None if exclude else len(value), "sha256": None if exclude else digest}) + if hidden: + return None + return safe_text + if isinstance(value, str): + safe_text, _ = transport_value(value, path, transport_url) + return redact(safe_text, path, value) + if isinstance(value, bool) or value is None: + return value + if isinstance(value, (int, float)): + literal = repr(value) + if any(secret in literal for secret in secrets): + redactions.append({"pointer": path, "reason": "explicit-sensitive-value"}) + return None + numbers.append({"pointer": path, "producer_type": "integer" if isinstance(value, int) else "float", "literal": literal}) + return value if (abs(value) <= 9007199254740991 if isinstance(value, int) else math.isfinite(value)) else None + if isinstance(value, (list, tuple)): + fields = re.fullmatch(r"/(?:backup/)*(?:request|response)/(?:headers|trailers)", path) + if fields: + result = [] + for index, field in enumerate(value): + if not isinstance(field, (list, tuple)) or len(field) != 2 or not isinstance(field[0], bytes): + raise CaptureFailure("format", "Native header/trailer is not an ordered byte pair.", pointer(path, index)) + result.append([visit(field[0], pointer(pointer(path, index), 0), depth + 2), visit(field[1], pointer(pointer(path, index), 1), depth + 2, field[0].lower() in CREDENTIALS, field[0].lower() in {b"location", b"referer", b"origin"})]) + return result + return [visit(item, pointer(path, index), depth + 1) for index, item in enumerate(value)] + if isinstance(value, dict): + result = {} + for key, item in value.items(): + if not isinstance(key, str): + raise CaptureFailure("format", "Native record has a non-string dictionary key.", path) + if key == "__proto__": + raise CaptureFailure("unsupported", "Native object contains a __proto__ member that the current JSON schema boundary cannot preserve.", path) + if any(secret in key for secret in secrets): + # Validate the omitted subtree too, then discard all of its + # coordinates rather than inventing a replacement identity. + validate_omitted(item, pointer(path, key), depth + 1) + redactions.append({"pointer": path, "reason": "explicit-sensitive-value", "scope": "property-name"}) + continue + result[key] = visit(item, pointer(path, key), depth + 1) + return result + raise CaptureFailure("decoder", "Native parser returned an unexpected value type: " + type(value).__name__ + ".", path) + + return {"reported": visit(state, ""), "binary_fields": binaries, "numeric_literals": numbers, "redactions": redactions} + + +def decode(request): + if version.VERSION != PROFILE: + raise CaptureFailure("unsupported", "Expected mitmproxy " + PROFILE + "; observed " + version.VERSION + ".") + records = [] + capture = Path(request["snapshot_path"]) + size = capture.stat().st_size + with capture.open("rb") as handle: + reader = BoundedReader(handle, size) + while handle.tell() < size: + start = handle.tell() + try: + state = tnetstring.load(reader) + except CaptureFailure: + raise + except RecursionError: + raise CaptureFailure("input-limit", "Native parser exceeded its recursion budget at byte offset " + str(start) + ".") from None + except (ValueError, IndexError, UnicodeError, EOFError): + raise CaptureFailure("format", "Malformed native record at byte offset " + str(start) + ".") from None + if not isinstance(state, dict): + raise CaptureFailure("format", "Native flow record must be a dictionary at byte offset " + str(start) + ".") + records.append({"ordinal": len(records), "location": {"kind": "byte-range", "offset": start, "bytes": handle.tell() - start}, **project_record(state, request["sensitive_values"]), "limitations": ["Fields are original native states without FlowReader migration. Binary fields contain exact producer bytes; reported UTF-8 strings for those fields are derived display views. Unknown flow/version extensions are retained without interpretation."]}) + return {"decoder": {"id": "mitmproxy-native-tnetstring", "name": "REA offline native mitmproxy adapter", "version": PROFILE}, "container": {"reported": None, "numeric_literals": [], "redactions": [], "records_pointer": None}, "total_records": len(records), "records": records} + + +def failure_reply(error): + """Keep producer constraints, resource exhaustion and adapter defects distinct.""" + if isinstance(error, CaptureFailure): + return {"ok": False, "reason": error.reason, "message": str(error), "pointer": error.pointer} + if isinstance(error, MemoryError): + return {"ok": False, "reason": "resource-limit", "message": "Native decoder exhausted its 768 MiB address-space budget.", "pointer": ""} + return {"ok": False, "reason": "decoder", "message": "Offline native decoder failed unexpectedly: " + type(error).__name__ + ".", "pointer": ""} + + +class OfflineCapture: + def load(self, loader): + loader.add_option("rea_request_path", str, "", "REA private offline capture request") + + def running(self): + resource.setrlimit(resource.RLIMIT_AS, (768 * 1024 * 1024, 768 * 1024 * 1024)) + resource.setrlimit(resource.RLIMIT_CPU, (30, 30)) + resource.setrlimit(resource.RLIMIT_FSIZE, (OUTPUT_BYTES, OUTPUT_BYTES)) + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + request = json.loads(Path(ctx.options.rea_request_path).read_text(encoding="utf-8")) + try: + response = {"ok": True, "value": decode(request)} + except Exception as error: + response = failure_reply(error) + try: + try: + output = json.dumps(response, ensure_ascii=True, allow_nan=False, separators=(",", ":")).encode("ascii") + except Exception as error: + output = json.dumps(failure_reply(error), separators=(",", ":")).encode("ascii") + if len(output) > OUTPUT_BYTES: + output = b'{"ok":false,"reason":"limit","message":"Native reply exceeds the complete-evidence output budget.","pointer":""}' + Path(request["reply_path"]).write_bytes(output) + finally: + ctx.master.shutdown() + + +addons = [OfflineCapture()] diff --git a/bridge/native/NativeUIChildren.swift b/bridge/native/NativeUIChildren.swift index 8e2afcf9a..10ac1d174 100644 --- a/bridge/native/NativeUIChildren.swift +++ b/bridge/native/NativeUIChildren.swift @@ -1,8 +1,24 @@ +import Foundation + struct ChildBatch { let values: [Element] let complete: Bool } +struct ChildCount { + let value: Int? + let error: Int32? +} + +func serializeHelperOutput(_ value: Any) throws -> Data { + try JSONSerialization.data(withJSONObject: value, options: [.sortedKeys, .withoutEscapingSlashes]) +} + +func captureChildCount(status: Int32, success: Int32, value: Int) -> ChildCount { + guard status == success else { return ChildCount(value: nil, error: status) } + return ChildCount(value: value, error: nil) +} + func captureChildValues(requestedCount: Int, copy: () -> [Element]?) -> ChildBatch { guard requestedCount > 0 else { return ChildBatch(values: [], complete: true) } let returned = copy() ?? [] diff --git a/bridge/native/ReaNativeUI.swift b/bridge/native/ReaNativeUI.swift index 05a24ccf5..2e4cc8341 100644 --- a/bridge/native/ReaNativeUI.swift +++ b/bridge/native/ReaNativeUI.swift @@ -22,9 +22,10 @@ func attribute(_ element: AXUIElement, _ key: String) -> CFTypeRef? { var value: CFTypeRef? return AXUIElementCopyAttributeValue(element, key as CFString, &value) == .success ? value : nil } -func childCount(_ element: AXUIElement) -> Int { +func childCount(_ element: AXUIElement) -> ChildCount { var count = 0 - return AXUIElementGetAttributeValueCount(element, kAXChildrenAttribute as CFString, &count) == .success ? count : 0 + let status = AXUIElementGetAttributeValueCount(element, kAXChildrenAttribute as CFString, &count) + return captureChildCount(status: status.rawValue, success: AXError.success.rawValue, value: count) } func children(_ element: AXUIElement, count: Int) -> ChildBatch { captureChildValues(requestedCount: count) { @@ -88,7 +89,11 @@ func observe(_ request: Request) async throws -> [String: Any] { var element = root for index in action.path ?? [] { var selectedChild: CFArray? - guard index >= 0 && index < childCount(element), + let count = childCount(element) + guard let available = count.value else { + try fail("element-count-unavailable", "Cannot validate selected accessibility path because child count failed with AXError \(count.error ?? -1)") + } + guard index >= 0 && index < available, AXUIElementCopyAttributeValues(element, kAXChildrenAttribute as CFString, index, 1, &selectedChild) == .success, let item = (selectedChild as? [AXUIElement])?.first else { try fail("element-missing", "Selected accessibility path no longer exists") } element = item @@ -104,15 +109,20 @@ func observe(_ request: Request) async throws -> [String: Any] { } guard outcome == .success else { try fail("action-failed", "Accessibility action failed with AXError \(outcome.rawValue)") } } - var nodes: [[String: Any]] = []; var truncated = false + var nodes: [[String: Any]] = []; var truncated = false; var gaps: [String] = [] if request.accessibility, let root = selected { var pending: [(AXUIElement, [Int])] = [(root, [])] while let (element, path) = pending.popLast() { if nodes.count >= request.max_nodes { truncated = true; break } - let totalChildren = childCount(element) + let childCountResult = childCount(element) var actions: CFArray? AXUIElementCopyActionNames(element, &actions) - nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": text(element, kAXValueAttribute), "actions": actions as? [String] ?? [], "children_count": totalChildren]) + nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": text(element, kAXValueAttribute), "actions": actions as? [String] ?? [], "children_count": childCountResult.value.map { $0 as Any } ?? NSNull()]) + guard let totalChildren = childCountResult.value else { + truncated = true + gaps.append("AX child count unavailable at path \(path): AXError \(childCountResult.error ?? -1)") + continue + } if path.count >= 32 { if totalChildren > 0 { truncated = true }; continue } let available = max(0, request.max_nodes - nodes.count - pending.count) let count = min(available, totalChildren) @@ -143,18 +153,18 @@ func observe(_ request: Request) async throws -> [String: Any] { } screenshot = ["mime_type": "image/png", "base64": png.base64EncodedString(), "sha256": SHA256.hash(data: png).map({ String(format: "%02x", $0) }).joined(), "width": image.width, "height": image.height] } - return ["window": ["pid": request.pid, "window_id": request.window_id, "executable": request.executable, "launch_time": launch, "title": window[kCGWindowName as String] as? String ?? ""], "nodes": nodes, "truncated": truncated, "screenshot": screenshot, "gaps": request.accessibility ? [] : ["Accessibility capture was disabled"]] + return ["window": ["pid": request.pid, "window_id": request.window_id, "executable": request.executable, "launch_time": launch, "title": window[kCGWindowName as String] as? String ?? ""], "nodes": nodes, "truncated": truncated, "screenshot": screenshot, "gaps": request.accessibility ? gaps : ["Accessibility capture was disabled"]] } Task { @MainActor in do { let request = try JSONDecoder().decode(Request.self, from: Data(CommandLine.arguments[1].utf8)) let result = try await observe(request) - let output = try JSONSerialization.data(withJSONObject: ["ok": true, "result": result], options: [.sortedKeys]) + let output = try serializeHelperOutput(["ok": true, "result": result]) FileHandle.standardOutput.write(output) } catch { let failure = error as? BoundaryFailure let output: [String: Any] = ["ok": false, "code": failure?.code ?? "capture-failed", "message": failure?.message ?? error.localizedDescription] - if let data = try? JSONSerialization.data(withJSONObject: output, options: [.sortedKeys]) { FileHandle.standardOutput.write(data) } + if let data = try? serializeHelperOutput(output) { FileHandle.standardOutput.write(data) } } exit(0) } diff --git a/bridge/process/ProcessRunTokenReader.swift b/bridge/process/ProcessRunTokenReader.swift new file mode 100644 index 000000000..f5c8da490 --- /dev/null +++ b/bridge/process/ProcessRunTokenReader.swift @@ -0,0 +1,263 @@ +import Darwin +import Foundation + +struct RunTokenObservation: Encodable { + let pid: Int32 + let state: String + let run_id: String? + let reason: String? +} + +struct ProcessIdentityObservation: Encodable { + let pid: Int32 + let state: String + let identity: String? + let reason: String? +} + +private struct ProcessMetadata { + let identity: String + let pointerSize: Int +} + +struct ProcessSnapshot { + fileprivate let metadata: [Int32: ProcessMetadata] + let failure: String? +} + +typealias ProcessTableSysctlCall = ( + _ mib: inout [Int32], + _ output: UnsafeMutableRawPointer?, + _ size: inout Int +) -> Int32 + +private func readProcessSnapshot() -> ProcessSnapshot { + readProcessSnapshot { mib, output, size in + sysctl(&mib, u_int(mib.count), output, &size, nil, 0) + } +} + +func readProcessSnapshot( + sysctlCall: ProcessTableSysctlCall +) -> ProcessSnapshot { + for _ in 0..<3 { + var mib = [CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0] + var size = 0 + guard sysctlCall(&mib, nil, &size) == 0 else { + let failure = errno + return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(failure)") + } + guard size >= MemoryLayout.size, + size % MemoryLayout.stride == 0 else { + return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(errno)") + } + var processes = [kinfo_proc]( + repeating: kinfo_proc(), + count: size / MemoryLayout.stride + ) + let result = processes.withUnsafeMutableBufferPointer { buffer in + let output = buffer.baseAddress.map { UnsafeMutableRawPointer($0) } + return sysctlCall(&mib, output, &size) + } + guard result == 0 else { + let failure = errno + if failure == ENOMEM { continue } + return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(failure)") + } + guard size % MemoryLayout.stride == 0 else { + return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(errno)") + } + let processCount = size / MemoryLayout.stride + let metadata = Dictionary( + processes.prefix(processCount).map { process in + let start = process.kp_proc.p_starttime + return ( + process.kp_proc.p_pid, + ProcessMetadata( + identity: "\(start.tv_sec):\(start.tv_usec)", + pointerSize: process.kp_proc.p_flag & P_LP64 != 0 ? 8 : 4 + ) + ) + }, + uniquingKeysWith: { first, _ in first } + ) + return ProcessSnapshot(metadata: metadata, failure: nil) + } + return ProcessSnapshot(metadata: [:], failure: "process_table_failed_\(ENOMEM)") +} + +func readProcessIdentities(pids: [Int32]) -> [ProcessIdentityObservation] { + let snapshot = readProcessSnapshot() + return pids.map { pid in + guard let metadata = snapshot.metadata[pid] else { + return unavailableIdentity( + pid: pid, + reason: snapshot.failure ?? "process_not_in_snapshot" + ) + } + return ProcessIdentityObservation(pid: pid, state: "readable", identity: metadata.identity, reason: nil) + } +} + +private func unavailableIdentity(pid: Int32, reason: String) -> ProcessIdentityObservation { + ProcessIdentityObservation(pid: pid, state: "unavailable", identity: nil, reason: reason) +} + +enum RunTokenReadError: Error { + case invalidBuffer + case duplicateToken + case invalidTokenEncoding + case environmentUnavailable + case appleVectorUnavailable + case ambiguousEnvironmentBoundary +} + +private func readRunToken(pid: Int32, pointerSize: Int) -> RunTokenObservation { + var mib = [CTL_KERN, KERN_PROCARGS2, Int32(pid)] + var size = 0 + guard sysctl(&mib, u_int(mib.count), nil, &size, nil, 0) == 0, size >= MemoryLayout.size else { + return unavailable(pid: pid, reason: "sysctl_failed_\(errno)") + } + var bytes = [UInt8](repeating: 0, count: size) + let result = bytes.withUnsafeMutableBytes { buffer in + sysctl(&mib, u_int(mib.count), buffer.baseAddress, &size, nil, 0) + } + guard result == 0 else { + return unavailable(pid: pid, reason: "sysctl_failed_\(errno)") + } + bytes = Array(bytes.prefix(size)) + do { + return RunTokenObservation(pid: pid, state: "readable", run_id: try token(in: bytes, pointerSize: pointerSize), reason: nil) + } catch RunTokenReadError.duplicateToken { + return unavailable(pid: pid, reason: "duplicate_run_token") + } catch RunTokenReadError.invalidTokenEncoding { + return unavailable(pid: pid, reason: "invalid_run_token_encoding") + } catch RunTokenReadError.environmentUnavailable { + return unavailable(pid: pid, reason: "environment_unavailable") + } catch RunTokenReadError.appleVectorUnavailable { + return unavailable(pid: pid, reason: "apple_vector_unavailable") + } catch RunTokenReadError.ambiguousEnvironmentBoundary { + return unavailable(pid: pid, reason: "ambiguous_environment_boundary") + } catch { + return unavailable(pid: pid, reason: "malformed_procargs") + } +} + +func readRunTokens(pids: [Int32]) -> [RunTokenObservation] { + let before = readProcessSnapshot() + let observations = pids.map { pid -> RunTokenObservation in + guard let metadata = before.metadata[pid] else { + return unavailable(pid: pid, reason: before.failure ?? "process_not_in_snapshot") + } + return readRunToken(pid: pid, pointerSize: metadata.pointerSize) + } + let after = readProcessSnapshot() + return zip(pids, observations).map { pid, observation in + guard let initial = before.metadata[pid], let current = after.metadata[pid] else { + return unavailable(pid: pid, reason: after.failure ?? "process_not_in_snapshot") + } + guard initial.identity == current.identity, + initial.pointerSize == current.pointerSize else { + return unavailable(pid: pid, reason: "process_identity_changed_during_token_read") + } + return observation + } +} + +func token(in bytes: [UInt8], pointerSize: Int = MemoryLayout.size) throws -> String? { + guard bytes.count >= MemoryLayout.size else { throw RunTokenReadError.invalidBuffer } + let argc = bytes.withUnsafeBytes { $0.loadUnaligned(as: Int32.self).littleEndian } + guard argc >= 0, Int(argc) <= bytes.count else { throw RunTokenReadError.invalidBuffer } + var offset = MemoryLayout.size + guard let executablePath = readString(in: bytes, offset: &offset) else { + throw RunTokenReadError.invalidBuffer + } + guard pointerSize == 4 || pointerSize == 8 else { throw RunTokenReadError.invalidBuffer } + let pathRecordSize = Array("executable_path=".utf8).count + executablePath.count + 1 + let padding = (pointerSize - pathRecordSize % pointerSize) % pointerSize + guard offset + padding <= bytes.count, + bytes[offset..<(offset + padding)].allSatisfy({ $0 == 0 }) else { + throw RunTokenReadError.invalidBuffer + } + offset += padding + for _ in 0..] = [] + var zeroedAppleVectorStart: Int? + while offset < bytes.count { + if bytes[offset] == 0 { + let start = offset + while offset < bytes.count, bytes[offset] == 0 { + offset += 1 + } + if offset - start > 1 { + zeroedAppleVectorStart = zeroedAppleVectorStart ?? trailingEntries.count + } + continue + } + guard let entry = readString(in: bytes, offset: &offset) else { + throw RunTokenReadError.invalidBuffer + } + trailingEntries.append(entry) + } + // XNU appends its Apple vector after the environment. libc clears the + // initial `pfz=` record after startup, leaving a multi-NUL run in its place. + // A caller can also set `pfz`, so prefer the final intact sentinel when it + // remains available and otherwise stop at the cleared record. + let intactAppleVectorStart = trailingEntries.lastIndex(where: { + $0.starts(with: Array("pfz=".utf8)) + }) + let preferZeroedBoundary = zeroedAppleVectorStart.flatMap { zeroedStart in + intactAppleVectorStart.map { intactStart in intactStart < zeroedStart } ?? true + } ?? false + let appleVectorStart = preferZeroedBoundary + ? zeroedAppleVectorStart + : intactAppleVectorStart + guard let appleVectorStart else { + throw RunTokenReadError.appleVectorUnavailable + } + if trailingEntries[appleVectorStart...].contains(where: { + $0.starts(with: Array("REA_PROCESS_RUN_ID=".utf8)) + }) { + throw RunTokenReadError.ambiguousEnvironmentBoundary + } + let environmentEntries = trailingEntries[..Ghidra discovery, profile & function analysis
snapshot, Unix/Windows transport, bounded lifecycle"] IDA["src/ida/
Upstream MCP contract adaptation
live GUI binding or owned headless database"] IDA_MCP["External ida-pro-mcp
GUI proxy or database supervisor
upstream installation and engine lifecycle"] - AUX["src/artifacts/ + src/native/
disjoint artifact and native capabilities"] + AUX["src/artifacts/ + src/native/
artifact inventory and extraction
format readers and native capabilities"] BROWSER["src/browser/
CDP / Playwright capture
captured scripts and native module resolution
execution/: coverage, listener and source attribution"] JS_CODEC["src/javascript/sourceMaps/
pinned upstream map codec
owned heap-limited Node process, complete point evidence"] INSPECTOR["src/inspector/
passive V8 Inspector discovery and capture
script and target locations"] diff --git a/docs/browser-observation.md b/docs/browser-observation.md index cea8f79a6..3be1b37f1 100644 --- a/docs/browser-observation.md +++ b/docs/browser-observation.md @@ -133,7 +133,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex - URL observations preserve the complete local URL, including query values, fragments, duplicate parameters, and their order. Only username/password userinfo is removed; malformed original URL strings remain available with `origin: null`. - DOM snapshots retain node types, node names, value lengths, and attribute names, but not text or attribute values. - Accessibility structure and roles are retained by default, while names and descriptions require `include_accessibility_text: true`. -- Network observations retain method, status, MIME type, size, type, initiator stack location, and complete URLs with userinfo credentials removed. Headers are discarded after an allowlisted projection of length/encoding, structured CSP/Link/policy fields, and untrusted agent hints. Cookies and authorization headers are never retained. +- Network observations retain method, status, MIME type, size, type, initiator stack location, and complete URLs with userinfo credentials removed. Redirects reported by CDP are attached as ordered `redirects` on the one request ID, with the prior request URL, CDP response URL, method/type, response status/MIME/encoded length, request timestamp, and the next `requestWillBeSent` event timestamp. CDP delivers `redirectResponse` with that next request event, so the latter is an event boundary rather than an exact response timestamp. Redirect response headers and bodies are discarded; hops outside the selected origins are discarded with the request chain. Headers are discarded after an allowlisted projection of length/encoding, structured CSP/Link/policy fields, and untrusted agent hints. Cookies and authorization headers are never retained. - Request/response bodies are not requested or parsed by default. When selected, only allowed-origin JSON media types are read, converted immediately to complete value-free property paths/types, and discarded. `Network.getResponseBody` is never sent unless JSON body shape capture is selected. - Console observations with a stack source retain call type, argument types, timestamp, and source location. When console text capture is selected, already-delivered primitive values are retained verbatim; objects, getters, and remote properties are never expanded. - WebSocket observations retain direction, opcode, and payload byte length. When shape capture is selected, text frames are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw frames are never retained. diff --git a/docs/capability-migration.md b/docs/capability-migration.md index 2ce6471a4..1a757dd82 100644 --- a/docs/capability-migration.md +++ b/docs/capability-migration.md @@ -385,6 +385,30 @@ claims. Actual Swift/Xcode, assetutil, native NIB, bundle signing and DMG lifecy claims require the real Apple lane. Native binary API/value-flow semantics and shared cross-domain contract aggregates keep their separate owners. +## Artifact inventory and extraction ownership + +`src/artifacts/inventory/` owns format classification, reader selection, inventory +policy, canonical scanning and artifact graph construction. The shared artifact +provider and JavaScript reconstruction workflow consume this owner. Snapshot +semantics remain in `src/domain/artifactInventorySnapshot.ts`. + +`src/artifacts/extraction/` owns member selection, integrity checks and transactional +materialization through the existing readers and safe output tree. Reader cleanup, +cancellation, rollback, archive containment and caller-visible results retain their +existing behavior. Acquisition guards reject imports from these producer owners +into application workflows, production composition or CLI/MCP adapters. + +`src/application/artifacts/ArtifactExtractionDestination.ts` owns the caller's +default temporary output-root choice for both CLI and MCP. The adapters pass this +destination to the existing provider operation; format handling and member writes +remain in the artifact owner. + +Add format-specific acquisition behavior beside the inventory readers and exercise +its actual producer representation in filesystem boundary tests. Add caller +workflow behavior in the application owner and verify both adapters. The installed +package lane checks inventory identity and extraction through CLI and MCP; the +real Apple bundle lane separately establishes DMG mount and detach behavior. + ## Binary production composition closure `src/composition/binary.ts` is the shared production owner for deep candidate @@ -425,3 +449,29 @@ native-value verifier locations are retained until their own proof lanes migrate interactive macOS UI success and real dependency tracing require their respective host/engine workflows. Portable source fixtures and injected call-route tests establish their narrower boundaries, without expanding provider/platform support. + +## Process capture and analyst workflow ownership + +`src/domain/process/` owns scenario/capture validation, portable process trees, +trace specifications, observations, comparisons and the canonical process Evidence +identity. The standalone contract example lives in `src/contracts/process/`; +shared session contracts retain their broader investigation owner. + +`src/process/capture/` owns the actual PTY boundary, terminal rendering, sampling, +selected child environment, filesystem snapshots/effects, runtime path admission, +event journal, settlement and resource cleanup. It reuses `src/process/` ownership +primitives and the Windows host substrate. Its identity import follows the +existing pure declaration directly. A source guard rejects outward application, +composition and caller imports; no new provider or lifecycle interface is added. + +`src/application/process/` retains Evidence projection and file-backed CLI +capture/comparison workflows. Capture helper tests use the forked adapter lane; +Evidence/host integration tests use the serial process-boundary lane. Their +fixtures and assertions remain unchanged. Installed capability-probe URLs follow +the new compiled adapter path, including the optional-dependency failure probe. + +Windows PTY capture retains its existing unavailable outcome because descendant +cleanup is not yet verified. Native Job Objects and a PTY binary do not establish +that capture workflow. Real POSIX capture/cleanup and installed terminal/PTY module +resolution require their actual host and package lanes; portable comparison +fixtures establish their separate evidence semantics. diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 000000000..ae7da8660 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,184 @@ +# CLI and Evidence + +Use the CLI for a direct inspection or a script. It uses the same application +workflows and evidence contracts as REA's MCP server. Each CLI invocation is a +separate process; save results when you want to use them in a later command. + +## Run a command + +After [installing REA](installation.md), use `rea`. You can also run a command +without a global installation: + +```bash +npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json +``` + +Use an extracted application directory or an ASAR as the target. Static +JavaScript analysis returns the application graph and Evidence directly. +Generic `rea analyze PATH` selects this workflow for directories and `.asar` +files when neither `--provider` nor `--snapshot` is supplied. See +[JavaScript artifact reconstruction](javascript-artifact-reconstruction.md) +for results, integrity checks and coverage. + +## Native analysis + +Configure [Hopper or Ghidra](installation.md#hopper), or the +[IDA adapter](ida-provider.md), before analyzing a native target. Substitute +your target, search text and function name or address in these examples: + +```bash +rea analyze /absolute/path/to/program --provider ghidra --json +rea search /absolute/path/to/program "search" --provider ghidra --json +rea function /absolute/path/to/program main --provider ghidra --json +rea decompile /absolute/path/to/program 0x1000 --provider ghidra --json +rea xrefs /absolute/path/to/program 0x1000 --provider ghidra --json +rea trace /absolute/path/to/program "search" --provider ghidra --json +``` + +`analyze` and `inspect` share the native overview workflow. `function` returns +a function dossier; `decompile` returns pseudocode. For assembly instructions +alone, use `rea instructions`. A macOS `.app` bundle can be supplied directly. + +Run `rea --help` or a command's `--help` for arguments and output options. +The [generated catalog](product-catalog.json) lists all CLI commands and MCP +tools. Provider-specific target support is described in the +[native guide](native-investigation.md), [DOS guide](ghidra-dos.md), and +[Windows Ghidra guide](windows-ghidra-p0.md). + +## Choose a provider + +List the available providers and their supported operations: + +```bash +rea providers --json +rea capabilities --json +``` + +These commands describe binary-session providers and auxiliary capabilities. +For the complete MCP surface, use the connected server's tool list and the +session's advertised availability; individual guides describe prerequisites. + +With automatic selection, REA uses the single available provider that supports +the target. When several providers support it, select one explicitly: + +```bash +rea analyze /absolute/path/to/program --provider hopper +``` + +Set `REA_ANALYSIS_PROVIDER` for a standing preference. An explicit `--provider` +overrides it. In MCP, pass `provider_id` to `open_binary`: + +```json +{ + "path": "/absolute/path/to/program", + "provider_id": "hopper" +} +``` + +The selected provider remains bound to the session until an explicit switch +or close. Provider failures are returned with their original reason. For an +`ambiguous` selection error, choose from `details.candidate_ids`; for +`provider_unavailable`, run `rea doctor --provider ID --json` to diagnose the +selected engine. See [task readiness](installation.md#check-readiness-for-your-task) +and [provider selection](adr/0001-provider-selection-and-analysis-profiles.md). + +The session reports work still in progress through `analysis_activity`. +A client timeout can end its wait while the provider continues analyzing. +`cleanup_incomplete` identifies resources whose shutdown or removal could not +be verified. See [MCP contracts](mcp-contracts.md) for session lifecycle and +[Ghidra first-query deadlines](mcp-contracts.md#ghidra-first-query-deadlines-and-recovery) +for import, client timeouts and recovery. + +## Save and reuse analysis snapshots + +A snapshot retains successful analysis results for later queries. REA reuses +an exact result when the target bytes, operation, parameters, provider and +settings match. Mutations and cursor-dependent calls are excluded from the +cache. Snapshot files are local and use owner-only permissions. + +```bash +rea analyze /absolute/path/to/program --provider ghidra --snapshot /absolute/path/to/analysis/program.json +# Repeat the same query to reuse its saved result. +rea analyze /absolute/path/to/program --provider ghidra --snapshot /absolute/path/to/analysis/program.json +``` + +An exact CLI cache hit is read before starting a provider process. +In MCP, `open_binary` accepts `snapshot_path` to import a snapshot atomically +for its matching target; an MCP provider may still start before returning a +cached result. `close_binary` accepts `snapshot_path` and optional +`overwrite: true` to save before releasing provider resources. A failed save +leaves the session open so the caller can resolve the output failure. + +## Import, export and compare Evidence + +Evidence records retain artifact identity, source locations, observations, +inferences and unresolved findings. Validate an existing bundle, export its +canonical form, or compare two supplied bundles: + +```bash +rea evidence-import /absolute/path/to/evidence/bundle.json +rea evidence-export /absolute/path/to/evidence/bundle.json /absolute/path/to/evidence/canonical.json +rea compare /absolute/path/to/evidence/left.json /absolute/path/to/evidence/right.json +``` + +Exports preserve an existing destination unless `--overwrite` is explicit. +For application traces and version comparisons, see +[JavaScript application workflows](javascript-application-workflows.md). +MCP [retained Evidence references](mcp-contracts.md#retained-application-evidence-inputs) +belong to one connection. Separate CLI calls consume full saved records. + +## Import historical source + +Import an older source tree to compare with the current artifact: + +```bash +rea import-reference-source /absolute/path/to/source +``` + +The import records hashes and metadata for the supplied files, separately +from observations of the current app. File names do not automatically exclude +files. Set `REA_REFERENCE_SECRET_PATTERNS_JSON` to a JSON array of ignore +patterns when you want to exclude selected paths. + +Historical-source import requires safe no-follow file opens on Linux or +macOS. Native Windows returns `unsupported_host`; use Linux REA inside WSL +or another supported host. See +[source-to-bundle comparison](javascript-application-workflows.md#historical-source-to-bundle-comparison) +for mapping identities, inferences and unknowns. + +## Capture runtime behavior + +Choose the guide for the target and intended interaction: + +- [Browser observation](browser-observation.md): inspect a selected page through CDP. +- [Browser scenarios](browser-scenario-contract.md): run declared interactions and capture their results. +- [Electron observation](electron-observation.md): inspect an Electron renderer or capture an application scenario. +- [Node/Electron Inspector](javascript-runtime-observation.md): record script locations and execution contexts. +- [Process capture](process-capture.md): run an executable and compare terminal, exit and filesystem observations. + +Runtime requests name the target and actions. Launched targets run with your +user permissions; consult the chosen guide for host requirements and effects. + +## Output and exit status + +The default terminal format is TOON. Use `--json` when saving results for a +JSON consumer. Output selection and formatting do not change operation status. + +| Status | Meaning | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | The operation completed. Its result may include partial evidence, warnings or unresolved questions. | +| `1` | The operation could not complete. Structured output identifies invalid input, permissions, cancellation, timeouts or another failure when available. | +| `128 + N` | Signal `N` ended the process, where the shell or runtime preserves the conventional signal-derived status. | + +`setup --dry-run` returns `planned` and exits `0`; a cancelled setup also exits +`0`. Setup returns `1` for `needs_confirmation` or `needs_human`. +`doctor` returns `1` when required checks in its selected readiness scope fail; +unavailable optional providers remain informational for unrelated tasks. + +Enable `pipefail` in a supporting shell so a downstream formatter preserves +REA's failure status: + +```bash +set -o pipefail +rea inspect-artifact ./app.asar --json | jq . > inspection.json +``` diff --git a/docs/error-contract.schema.json b/docs/error-contract.schema.json index 5f276b551..1d5f875cb 100644 --- a/docs/error-contract.schema.json +++ b/docs/error-contract.schema.json @@ -29,7 +29,75 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} + } + }, + "required": ["code", "category", "message", "retryable", "remediation"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "code": { "type": "string", "const": "access_denied" }, + "category": { + "type": "string", + "enum": [ + "invalid_input", + "unsupported_provider", + "integrity_mismatch", + "truncated", + "cancelled", + "timeout", + "unavailable", + "execution_failure" + ] + }, + "message": { "type": "string", "minLength": 1 }, + "retryable": { "type": "boolean" }, + "remediation": { + "type": "object", + "properties": { "action": { "type": "string", "minLength": 1 } }, + "required": ["action"], + "additionalProperties": false + }, + "details": { + "type": "object", + "propertyNames": { "type": "string" }, + "additionalProperties": {} + } + }, + "required": ["code", "category", "message", "retryable", "remediation"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "code": { "type": "string", "const": "artifact_changed" }, + "category": { + "type": "string", + "enum": [ + "invalid_input", + "unsupported_provider", + "integrity_mismatch", + "truncated", + "cancelled", + "timeout", + "unavailable", + "execution_failure" + ] + }, + "message": { "type": "string", "minLength": 1 }, + "retryable": { "type": "boolean" }, + "remediation": { + "type": "object", + "properties": { "action": { "type": "string", "minLength": 1 } }, + "required": ["action"], + "additionalProperties": false + }, + "details": { + "type": "object", + "propertyNames": { "type": "string" }, + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -63,7 +131,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -97,7 +165,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -131,7 +199,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -165,7 +233,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -199,7 +267,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -289,7 +357,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -323,7 +391,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -357,7 +425,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -391,7 +459,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -425,7 +493,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -459,7 +527,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -493,7 +561,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -527,7 +595,7 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], @@ -561,27 +629,11 @@ "details": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } + "additionalProperties": {} } }, "required": ["code", "category", "message", "retryable", "remediation"], "additionalProperties": false } - ], - "$defs": { - "__schema0": { - "anyOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" }, - { "type": "null" }, - { "type": "array", "items": { "$ref": "#/$defs/__schema0" } }, - { - "type": "object", - "propertyNames": { "type": "string" }, - "additionalProperties": { "$ref": "#/$defs/__schema0" } - } - ] - } - } + ] } diff --git a/docs/installation.md b/docs/installation.md index 9caa72c65..0821d0d60 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -68,13 +68,18 @@ selects the npm release, and persistent MCP registrations are pinned to the version that performed setup. Installing newer instructions does not update a running server or its registration. -The release checked on **2026-10-06** was **4.1.0** (125 MCP tools). That artifact -includes main's Windows native control bundle, Android/JADX and firmware -tools, Ghidra function annotation workflow, and retained application-Evidence -references. -Main's catalog describes the current code; a source build or a subsequent -release containing later changes is required for anything newer. Package startup alone does not verify a -provider's real platform workflow. +The release checked on **2026-10-07** was **5.0.0** (133 MCP tools), published +from the fixed checkpoint +[`b33236ec`](https://github.com/morluto/rea/releases/tag/rea-agents-5.0.0). +The public CLI, MCP catalog and target-free session, and isolated update from +4.1.0 to 5.0.0 were verified through npm. The artifact includes Windows native +controls, Android/JADX and firmware tools, Ghidra function annotations, and +retained application-Evidence references. + +Main's catalog describes the current code. A source build or a subsequent +release containing changes after this checkpoint is required for newer +functionality. Package startup alone does not verify a provider's real +platform workflow. To check the published version, run `npm view rea-agents dist-tags.latest`. Use the connected server's actual tool list and advertised input schemas for @@ -110,7 +115,7 @@ npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app -- It returns the complete Evidence record directly and requires no native engine. Provider failures in doctor do not prevent unrelated target-free tools. To check readiness for one task instead of auditing every integration, see -[Check readiness for the task at hand](https://github.com/morluto/rea/blob/main/README.md#check-readiness-for-the-task-at-hand). +[Check readiness for your task](#check-readiness-for-your-task). ## Supported agents @@ -213,12 +218,44 @@ installed REA skill. Run the returned scoped setup command to review and approve those changes, then restart affected agents. The plan is returned in terminal, non-TTY, and JSON modes without applying configuration changes. +## Check readiness for your task + +Run `rea doctor` when you need diagnosis. It reads host prerequisites and agent +configuration without changing them. Without options, it audits every detected +registration, the installed skill and optional analysis engines; its overall +`healthy` value can be false while your chosen workflow works. + +Select the part you want to check: + +| Task | Readiness check | +| ----------------------------------- | ------------------------------------------------------------------------ | +| Static JavaScript/Electron analysis | Run `rea analyze-javascript-application PATH --json` directly. | +| One analysis engine | `rea doctor --provider ghidra --json` (or `hopper`, `ida`) | +| One agent registration | `rea doctor --client codex --json` (see [client IDs](#supported-agents)) | +| Installed workflow instructions | `rea doctor --skill --json` | + +A scoped report has `scope.mode: "explicit"`. Its `scope_checks` determine +`healthy` and the exit status. Other checks appear in `informational_checks`; +`environment_healthy` summarizes the full audit. `--target` adds a target check, +but the report remains audit-wide unless a scope option is also supplied. + +Follow the remediation for the failed check. If a native target has several +available providers, choose one with `--provider` on the CLI or `provider_id` +on `open_binary`. A `capability_unavailable` failure carries +`details.selection_reason`: `ambiguous` asks you to choose from +`details.candidate_ids`, while `provider_unavailable` asks you to repair the +selected engine. See [provider selection](cli.md#choose-a-provider). + ## Hopper Hopper is separate commercial software with its own license. Its free demo has vendor-defined limits, and a paid license is optional. REA reuses any detected installation and preserves Hopper during uninstall. +The supported native host baseline is macOS 12+, Ubuntu 24.04+, Fedora 41+, +64-bit Arch Linux, or CachyOS. Ghidra and IDA have their own provider-specific +host requirements; Windows Ghidra uses the [experimental P0 boundary](windows-ghidra-p0.md). + On macOS, approved setup downloads the official DMG, checks its published size and digest, validates the application bundle, and atomically installs it to `~/Applications/Hopper Disassembler.app`. REA then opens Hopper so the @@ -247,6 +284,40 @@ host mount and the rest of `/tmp` remain unchanged; this fallback never invokes `sudo`. `rea doctor --provider hopper --json` reports the selected strategy and both host and effective mount facts. +### Launcher paths and troubleshooting + +On macOS, REA uses +`/Applications/Hopper Disassembler.app/Contents/MacOS/hopper` by default. +On Linux, it prefers executable `/opt/hopper/bin/Hopper`, then executable +`~/.local/share/rea/hopper/bin/Hopper`; if neither exists, the former remains +the diagnostic fallback. `HOPPER_LAUNCHER_PATH` overrides those choices: + +```bash +export HOPPER_LAUNCHER_PATH=/absolute/path/to/Hopper +rea doctor --provider hopper --json +``` + +If a Linux launcher exists but cannot start, inspect its shared libraries: + +```bash +ldd /absolute/path/to/Hopper | grep 'not found' +``` + +Install the missing packages and rerun the scoped check. Linux demo sessions +need Xvfb, Python 3, X11 and XTEST; approved setup installs those dependencies +on supported distributions. Add the curl installer's reported executable +directory, commonly `~/.local/bin`, to your shell `PATH` if needed. + +REA starts Hopper when needed. On macOS, its launcher can bring a window or +first-run dialog forward; choose demo mode or activate your existing license. +Hopper serializes analysis requests. Cancelling a wait can leave provider work +running, which the session reports. Successful decompilation is cached until +a relevant rename or comment changes it. + +Closing a session shuts down REA's bridge and removes its temporary socket +directory while preserving the Hopper application. A `cleanup_incomplete` +result identifies resources whose cleanup could not be verified. + ### Hopper in CI REA's unattended Linux path is validated against Hopper's offered demo mode. @@ -289,7 +360,7 @@ entry comments atomically and returns refreshed analysis. These session metadata edits leave executable bytes unchanged and are discarded on close. GUI controls require Hopper; Windows P0 remains read-only. -Windows P0 admits native x86-64 PE applications on fixed local NTFS volumes. +Windows P0 admits native x86 and x86-64 PE applications on fixed local NTFS volumes. The npm package bundles native Job Object ownership, protected private runtime DACLs, and handle-based path admission; no separate addon installation is needed. See the [Windows Ghidra P0 guide](windows-ghidra-p0.md) for verified scope and @@ -389,6 +460,17 @@ Ghidra/JDK installation; neither is inferred from startup alone. `rea doctor --json` is strictly read-only. `rea update` updates only the npm installation that owns the running CLI. Source checkouts and package-runner copies must be updated through the mechanism that owns them; a fresh package-runner invocation can use `npx rea-agents@latest`. `rea uninstall` removes only REA-owned agent registrations and skill files; `--purge-data` additionally removes REA cache and state paths. +```bash +rea update +rea uninstall +rea uninstall --purge-data +``` + +Uninstall preserves Hopper, Node.js, Evidence files, captures, unrelated skills +and other MCP servers. Purging removes only REA's cache and state under +`~/.rea`; malformed client configuration or purge-path symlinks stop the +operation. See the [CLI guide](cli.md#output-and-exit-status) for exit statuses. + ## MCP Registry REA is published in the official MCP Registry as `io.github.morluto/rea`. Registry @@ -408,7 +490,7 @@ For a client that requires manual configuration, use: "mcpServers": { "rea": { "command": "npx", - "args": ["-y", "rea-agents@4.1.0", "mcp"] + "args": ["-y", "rea-agents@5.0.0", "mcp"] } } } diff --git a/docs/javascript-artifact-reconstruction.md b/docs/javascript-artifact-reconstruction.md index 97d6cd41a..ccca4d12f 100644 --- a/docs/javascript-artifact-reconstruction.md +++ b/docs/javascript-artifact-reconstruction.md @@ -31,6 +31,11 @@ command and the generic route return their complete results inline. Native targets, single JavaScript files, `.app` bundles, and explicit deep-provider or snapshot requests retain the native deep-analysis route. +Both CLI routes emit `rea_progress` JSON lines on stderr as inventory, source +parsing, graph construction, validation, and Evidence hashing begin. Stdout +remains the selected result document. Completion is reported after Evidence +creation; a failed analysis retains its typed diagnostic result. + Configure an MCP client with the ordinary REA setup command: ```bash @@ -52,6 +57,21 @@ manifest and graph commitments, JavaScript Application Graph, static Electron summary, reconstruction statistics, and explicit limitations. It does not require a live Hopper, Ghidra, browser, or Electron process. +## ASAR integrity + +ASAR inventory checks Electron integrity metadata for embedded archive entries +and supplied `.asar.unpacked` companion files. An integrity failure identifies +the logical path, declared and calculated SHA-256 values, and whether the entry +was unpacked. By default, a mismatch is returned as a failure with its artifact +context. Requests that support `integrity_policy` can explicitly select +`record-and-continue` to inspect verified siblings while retaining the mismatch. + +An unpacked entry whose companion bytes were not supplied remains +`unavailable`. REA continues analyzing embedded JavaScript and records the +missing native/resource bytes as unknown. See [what is reconstructed](#what-is-reconstructed) +for the inventory fields and [MCP integrity handling](mcp-contracts.md#integrity-record-and-continue) +for the tool-result contract. + ## Large results Graph and Evidence identifiers hash canonical JSON incrementally, without @@ -60,11 +80,19 @@ identifiers remain unchanged. The opt-in regression check is `npm run verify:javascript:digests`; it hashes a value larger than the running Node engine's single-string limit. -Output formatters and MCP transport serialization still assemble whole strings. -For a large CLI result, select the needed fields -before formatting, for example `--format json --filter-output -evidence_id,normalized_result.statistics`. Complete serialization of a result -beyond the engine's string limit is not established by this digest check. +Both application CLI routes stream complete JSON and JSONL output in bounded +chunks, including field filters and `--full-output`. They wait for each stdout +write instead of constructing or reparsing a document-sized string. The opt-in +`npm run verify:javascript:output` checks the actual CLI formatting boundary with +a temporary output file larger than the running engine's string limit, verifies +its bytes and digest, and removes it. Use `-- jsonl` for the compact JSONL check. +Each check needs space for one output file plus a 1 GiB free-space reserve. + +Other CLI formats, `--token-count`, and MCP transport serialization still +assemble whole strings. Field selection remains useful when the caller needs a +smaller view, for example `--format json --filter-output +evidence_id,normalized_result.statistics`. Streaming output does not bound the +memory needed to construct the analysis graph itself. Client framing limits also apply: the pinned Node MCP SDK's stdio transport defaults to a 10 MiB buffer. Its caller-selected `maxBufferSize` must accommodate the complete response, including text and structured Evidence projections. @@ -193,3 +221,8 @@ paired, ambiguous, and unpaired IPC, validation candidates, utility processes, and native binding requests. These fixtures establish parser and artifact-reader claims; they do not replace the later operator-supplied real-application benchmark. + +URI schemes are classified independently of local file names: a reference such +as `web3:app.js` remains external even when an artifact has that literal name. +Scheme characters may include digits after the initial letter; a relative path +such as `./web3:app.js` still names a local artifact. diff --git a/docs/managed-code-analysis.md b/docs/managed-code-analysis.md index 336cf0c53..a405b1f4b 100644 --- a/docs/managed-code-analysis.md +++ b/docs/managed-code-analysis.md @@ -125,7 +125,7 @@ The upstream analyzer currently gates its own analysis to x86-64. NativeAOT's broader platform matrix does not establish REA coverage: PE/COFF, ELF, Mach-O, shared libraries, target architectures, and runtime metadata versions each need their own provider verification. REA's experimental Windows Ghidra boundary is -limited to admitted native x86-64 PE applications, so it does not currently +limited to admitted native x86 and x86-64 PE applications, so it does not currently establish coverage for PE DLLs. Mobile NativeAOT targets should remain experimental until REA has a matching provider and package-level verification. diff --git a/docs/mcp-contracts.md b/docs/mcp-contracts.md index 7dd8b8dfb..70bf2259c 100644 --- a/docs/mcp-contracts.md +++ b/docs/mcp-contracts.md @@ -13,6 +13,24 @@ are currently unavailable. Opening or closing a target or observing a provider health transition leaves that catalog unchanged and does not emit `notifications/tools/list_changed`. +Advertised input and output schemas contain no reachable recursive references. +The input compatibility profile limits nesting to ten object, array, and +`anyOf`/`oneOf`/`allOf` levels, following local references per path. Property +maps, reference definitions, and example data do not add schema levels. Tests +check the complete catalog after SDK conversion and its generated counterpart. +This is REA's local compatibility profile; individual model APIs can impose +additional limits. + +For passive `compare_web_captures` inputs, pass each complete +`inspect_web_page` result in `before.inspection` or `after.inspection`, with an +optional complete `discover_webmcp_tools` result in the matching `webmcp` field. +The input schema describes these producer-result objects as round-trip payloads. +For scenario comparisons, pass complete `capture_browser_scenario` results in +`before_scenario` and `after_scenario`. REA validates all nested fields with the +original capture schemas; observation output schemas remain complete. Scenario +authoring inputs and comparison normalization options retain their full +advertised structure. + Call `binary_session` with `{}` and read `result.tool_availability` to choose a callable operation for the current target, provider, host, and negotiated client capabilities. The default result includes the complete inventory with each diff --git a/docs/process-capture.md b/docs/process-capture.md index 8ed630205..38048a413 100644 --- a/docs/process-capture.md +++ b/docs/process-capture.md @@ -30,6 +30,14 @@ different recovery: reinstall REA for the active platform, architecture, and Node.js version with optional dependencies enabled. Capability diagnostics distinguish this from the Windows capture-adapter limitation. +macOS also requires Apple's Swift compiler through `xcrun` for process +ownership inspection. REA compiles its packaged, narrow process-inspection +helper into a private temporary directory before a capture or owned provider +process starts; the same prerequisite applies to owned provider-process +supervision on macOS. REA removes that directory when it exits. REA does not +install Xcode, Command Line Tools, or other software. Capability checks prepare +this helper before reporting macOS capture as available. + ## Capture a command Write a JSON scenario and pass its path to the CLI: diff --git a/docs/product-catalog.json b/docs/product-catalog.json index a4eb01127..75735d07f 100644 --- a/docs/product-catalog.json +++ b/docs/product-catalog.json @@ -1,16 +1,16 @@ { "package": { "name": "rea-agents", - "version": "4.1.0", + "version": "5.0.0", "sdk": { "server": "2.3.1", "client": "2.3.1", "core": "2.3.1" }, - "skill_version": "28" + "skill_version": "29" }, "tools": { - "total": 134, + "total": 135, "families": [ { "id": "direct", @@ -143,7 +143,7 @@ { "id": "browser", "surface": "browser-provider", - "count": 11, + "count": 12, "tools": [ "analyze_web_bundle", "capture_browser_scenario", @@ -152,6 +152,7 @@ "compare_web_screenshots", "discover_webmcp_tools", "inspect_web_event_listeners", + "inspect_web_network_capture", "inspect_web_page", "list_browser_targets", "observe_web_execution", @@ -269,6 +270,12 @@ "xrefs" ] }, + { + "id": "har-schema/lossless-json", + "name": "REA HAR capture adapter", + "version": "har-schema@2.0.0;jsonc-parser@3.3.1;lossless-json@4.3.1", + "capabilities": ["inspect_web_network_capture"] + }, { "id": "hopper", "name": "Hopper Disassembler", @@ -344,6 +351,12 @@ "trace_android_references" ] }, + { + "id": "mitmproxy-native-tnetstring", + "name": "REA offline native mitmproxy adapter", + "version": "12.2.3", + "capabilities": ["inspect_web_network_capture"] + }, { "id": "native-macos", "name": "macOS native inspection utilities", @@ -572,7 +585,7 @@ } ], "cli": { - "primary_count": 89, + "primary_count": 90, "commands": [ "address-to-file-offset", "analyze", @@ -629,6 +642,7 @@ "inspect-plist", "inspect-signature", "inspect-web-event-listeners", + "inspect-web-network-capture", "inspect-web-page", "instructions", "list-architectures", @@ -673,15 +687,15 @@ }, "runtime_catalog": { "counts": { - "cli_commands": 89, - "mcp_tools": 134, + "cli_commands": 90, + "mcp_tools": 135, "mcp_prompts": 6 }, "digests": { - "tools_sha256": "9ba18b85d37d4883392982eebeb463e2bfa5096fee55a35cf7b7dde30eae5c03", + "tools_sha256": "4266c0019ab7ddda340ceba2da9a62525368af1e6052d08cc0ef4a790be07882", "prompts_sha256": "ef2de8814390b340623c32001610aa2709d90bed51537b6e562ab3abf144ab4d", - "combined_sha256": "a9f3428463b46fece17dc43ad1aa913296e0b11cf3d57ce477e3b06e93b99d17", - "providers_sha256": "78627c8dd3657e15cd4a819dad747555e7aa76111ecce2393441d173aa5b3b88" + "combined_sha256": "bfa2ed377826f8e561d1b8a9024d4ab0cf11f09d8b1af86c80d2884c1d72dbf3", + "providers_sha256": "de4729a56a0e22b42f24803efefd625fa607b6af7de02d594b6dfaccf5a91fa0" } } } diff --git a/docs/provider-evaluation.md b/docs/provider-evaluation.md index 69a621419..baf574085 100644 --- a/docs/provider-evaluation.md +++ b/docs/provider-evaluation.md @@ -18,7 +18,7 @@ comments atomically, verifies readback, and returns refreshed function analysis. Changes live only in the session database and leave executable bytes unchanged. Ghidra does not expose GUI controls; Windows P0 remains read-only. -Windows x64 P0 supports native x86-64 PE applications on local NTFS with +Windows x64 P0 supports native x86 and x86-64 PE applications on local NTFS with bundled Job Object ownership, protected runtime DACLs, and handle-based path admission. Real ordinary-user verification covers all 25 read-only operations through the packaged CLI and MCP, target integrity, and cleanup. This remains @@ -49,8 +49,8 @@ follow. - Report GUI cursor/navigation and persistent mutation operations as unavailable until their semantics and project ownership are explicitly designed. - Verify real claims on Linux with at least two distinct source-owned binaries. - Verify the Windows host claim with the deterministic source-owned x86-64 PE - fixture and all admitted operations; compare normalized semantic facts rather + Verify the Windows host claim with the deterministic source-owned x86 and x86-64 PE + fixtures and all admitted operations; compare normalized semantic facts rather than provider-specific pseudocode text. ## Shipped foundation boundary @@ -62,7 +62,7 @@ maximum, when the release leaves those at the 12.1 defaults). Otherwise doctor probes `java`/`javac` or `java.exe`/`javac.exe` from `PATH`. Supported Linux and macOS hosts accept compatible ELF, PE, and Mach-O executable targets. Host admission and target compatibility are separate checks. Windows x64 P0 admits only native, -non-managed, non-DLL x86-64 PE applications on fixed local NTFS, with the packaged +non-managed, non-DLL x86 and x86-64 PE applications on fixed local NTFS, with the packaged native authority available before launch. The launcher creates one ephemeral runtime root with project, @@ -136,7 +136,7 @@ Mach-O target coverage. It requires `clang`, LLD, and `lld-link`; set these tools before compilation. Keeping this matrix separate lets Linux host/provider acceptance run with only the host compiler. -`npm run verify:ghidra:windows` checks the source-owned native x86-64 PE +`npm run verify:ghidra:windows` and its `-- --x86` variant check the source-owned native x86 and x86-64 PE fixture, all 25 read-only operations, target/snapshot/import SHA-256 linkage, and project, endpoint, process, and runtime cleanup. The independent native lane checks DACLs, handle admission, cancellation, and Job Object lifecycle. @@ -165,12 +165,12 @@ only the generic process mechanisms. ## Shortlist -| Provider | License / automation surface | What it brings | REA fit and blockers | -| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Ghidra](https://github.com/NationalSecurityAgency/ghidra) | Apache-2.0 source license; `analyzeHeadless`, Java APIs, and PyGhidra | Static analysis, multiple processors and formats, scripting, and project/database workflows | 25 read-only operations shipped on Linux/macOS with installation checks, temporary projects, a serial API queue, and real ELF/PE/Mach-O conformance. Experimental Windows x64 P0 supports native x86-64 PE on local NTFS with bundled native controls and ordinary-user CLI/MCP verification; Linux/macOS additionally support atomic session function annotations; Windows mutation and all Ghidra GUI operations are unavailable. | -| [Rizin](https://github.com/rizinorg/rizin) / [rz-pipe](https://github.com/rizinorg/rz-pipe) | Rizin repository contains LGPL-3.0 and GPL-3.0 components; `rizin`, `rz-bin`, and language bridges through `rzpipe` | Portable CLI analysis, disassembly/debugging, many architectures and file formats, JSON command output | Good candidate for a process-backed Linux provider and fast metadata fallback. License/component inventory must be preserved; command output needs version-pinned parsers and semantic conformance before evidence is trusted. | -| [LIEF](https://github.com/lief-project/LIEF) | Apache-2.0; C++, Python, and other bindings | Deterministic parsing and modification of ELF, PE, Mach-O, COFF, and related executable formats; headers, sections, symbols, relocations, and functions | Best near-term complement, not a decompiler replacement. It can cover format metadata and artifact evidence without a long-lived analysis process; function semantics, pseudocode, CFG, and cross-reference parity remain out of scope unless separately demonstrated. | -| [Binary Ninja](https://docs.binary.ninja/dev/index.html) | API/documentation components are MIT, while the analysis product is licensed by edition; commercial, Ultimate, or Headless license is required for headless automation | Python/Core/C++/Rust APIs, headless loading, IL layers, function analysis, plugins, and configurable analysis | Strong technical fit for a native provider, especially function dossiers. Commercial licensing, license-secret handling, native runtime packaging, and multithreaded lifecycle rules are material deployment blockers. | +| Provider | License / automation surface | What it brings | REA fit and blockers | +| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Ghidra](https://github.com/NationalSecurityAgency/ghidra) | Apache-2.0 source license; `analyzeHeadless`, Java APIs, and PyGhidra | Static analysis, multiple processors and formats, scripting, and project/database workflows | 25 read-only operations shipped on Linux/macOS with installation checks, temporary projects, a serial API queue, and real ELF/PE/Mach-O conformance. Experimental Windows x64 P0 supports native x86 and x86-64 PE on local NTFS with bundled native controls and ordinary-user CLI/MCP verification; Linux/macOS additionally support atomic session function annotations; Windows mutation and all Ghidra GUI operations are unavailable. | +| [Rizin](https://github.com/rizinorg/rizin) / [rz-pipe](https://github.com/rizinorg/rz-pipe) | Rizin repository contains LGPL-3.0 and GPL-3.0 components; `rizin`, `rz-bin`, and language bridges through `rzpipe` | Portable CLI analysis, disassembly/debugging, many architectures and file formats, JSON command output | Good candidate for a process-backed Linux provider and fast metadata fallback. License/component inventory must be preserved; command output needs version-pinned parsers and semantic conformance before evidence is trusted. | +| [LIEF](https://github.com/lief-project/LIEF) | Apache-2.0; C++, Python, and other bindings | Deterministic parsing and modification of ELF, PE, Mach-O, COFF, and related executable formats; headers, sections, symbols, relocations, and functions | Best near-term complement, not a decompiler replacement. It can cover format metadata and artifact evidence without a long-lived analysis process; function semantics, pseudocode, CFG, and cross-reference parity remain out of scope unless separately demonstrated. | +| [Binary Ninja](https://docs.binary.ninja/dev/index.html) | API/documentation components are MIT, while the analysis product is licensed by edition; commercial, Ultimate, or Headless license is required for headless automation | Python/Core/C++/Rust APIs, headless loading, IL layers, function analysis, plugins, and configurable analysis | Strong technical fit for a native provider, especially function dossiers. Commercial licensing, license-secret handling, native runtime packaging, and multithreaded lifecycle rules are material deployment blockers. | ## Recommended order diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 000000000..b42189f45 --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,138 @@ +# Releasing from a checkpoint + +REA releases use an explicit source checkpoint. Main can continue accepting +changes while a release is tested and published. The Release workflow runs +only when a maintainer dispatches it; main pushes do not refresh release PRs. + +## 1. Select the source + +Choose the next version from the unreleased Conventional Commits, including +breaking changes. Record the full source SHA and create `release/VERSION` at +that commit. The version is a maintainer choice; Release Please's proposed +version must agree before publication. + +For example, after selecting a reviewed commit for 5.0.0: + +```bash +git fetch origin main +git branch release/5.0.0 SOURCE_SHA +git push origin release/5.0.0 +``` + +Replace `SOURCE_SHA` with the selected full commit SHA. The checkpoint must +include this release workflow and CI support for release branches. For an +older checkpoint, backport only the release infrastructure first and record +that additional commit. Do not merge later implementation changes into the +release branch or rebase the candidate onto a moving main. + +## 2. Prepare the bot PR + +Run the workflow definition from main and select the frozen source separately: + +```bash +gh workflow run release.yml --ref main \ + -f release_branch=release/5.0.0 -f phase=prepare +``` + +Release Please targets that branch and prepares its version, changelog, and +registry metadata. The workflow uses an optional `RELEASE_PLEASE_TOKEN` +repository secret and otherwise uses the built-in `GITHUB_TOKEN`. A dedicated +GitHub App or personal access token can start PR CI automatically. With the +built-in token, `opened`, `synchronize`, and `reopened` PR events create runs +that require a maintainer with write access to approve them. See +[GitHub's workflow trigger documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow). + +The generation step normalizes the product catalog. Preparation cannot create +a GitHub release or publish a package. Record the final bot PR head after +normalization, then approve that head's blocked runs from the PR page. Updates +to the bot branch can create new approval-required runs; approval of an older +head does not verify the normalized candidate. Wait for the final head's CI. + +Review the candidate's version, notes, generated metadata, and package +contents. Wait for the candidate's CI and relevant real-provider checks. +Routine local iterations need focused checks; CI owns full deterministic +coverage and platform lanes. If an artifact or real-provider check is +unavailable, report that limit before deciding to publish. + +Merge the reviewed PR into `release/5.0.0` with its head SHA matched. Further +main commits do not change this candidate. A necessary release fix belongs +on the release branch, must be reviewed and tested, and establishes a new +recorded checkpoint. + +## 3. Publish the reviewed merge + +After the release PR has merged: + +```bash +gh workflow run release.yml --ref release/5.0.0 \ + -f release_branch=release/5.0.0 -f phase=publish +``` + +Publication creates the release from the merged bot PR without preparing or +updating another PR. Both npm and MCP Registry jobs check out Release Please's +exact release SHA. They do not build the current main tip or a mutable branch. +The publish dispatch runs from the frozen release branch so npm's provenance +records the actual release commit. Before Release Please creates a tag, the +workflow accepts only `prepare` or `publish` and requires the selected branch +tip to equal the dispatch SHA. Registry jobs run only during `publish`. After +a tag exists, a mismatch between that tag's SHA and the dispatch SHA stops +both registry publishes. +See [npm's provenance implementation](https://github.com/npm/cli/blob/v11.16.0/workspaces/libnpmpublish/lib/provenance.js) +for the use of GitHub's workflow ref and commit SHA. + +The workflow builds the bundled Windows controls and verifies the packaged +artifact before npm publication. It then verifies the published CLI, the +capability-scoped MCP catalog, and the isolated package update path before +publishing MCP Registry metadata. + +Record these outcomes separately: + +- GitHub release and tag, including the resolved commit SHA. +- npm's exact version and integrity, with the published-package canary passed. +- MCP Registry's exact server version and matching npm package version. + +A GitHub tag alone does not establish npm or MCP Registry publication. + +## 4. Sync metadata back to main + +Post-release synchronization is part of completing the release. Finish it +before cutting the next checkpoint; otherwise main retains the previous +release baseline and can propose an already-published version again. + +After publication, open a PR from the release branch back to main. Preserve +main's later implementation changes and resolve generated-file conflicts by +regenerating from the combined contracts with the released package version. +Review and test this synchronization PR, then use a merge commit so the release +tag remains in main's ancestry. Keep the released tag unchanged. + +The synchronization must include `.release-please-manifest.json`, +`package.json`, both root versions in `package-lock.json`, `CHANGELOG.md`, +`server.json`, and the versioned documentation examples. Run +`npm run docs:generate`, `npm run docs:check`, and the release configuration +tests against the combined tree. After the merge, verify that the released +tag is an ancestor of main: + +```bash +git fetch origin main --tags +git merge-base --is-ancestor rea-agents-5.0.0 origin/main +``` + +Close any superseded rolling release PR. Future releases repeat the checkpoint +procedure from main; never resume automatic release-PR refreshes on main pushes. +The generated-metadata workflow stays limited to pull requests into main so it +does not push commits onto a frozen candidate. Preparation normalizes +`docs/product-catalog.json` on the bot pull request. + +## Partial publication and retries + +Inspect the failed job and public registry state before retrying. When npm is +already published, the npm job verifies that exact version instead of +publishing it again. Re-run failed jobs in the original publication run so its +release SHA and outputs stay fixed. If only MCP publication failed, retry that +job after checking that the npm canary succeeded. + +A branch tip that moved after dispatch fails before Release Please creates a +tag. Do not dispatch a fresh publish phase to repair an already-created +release: Release Please will not create the same release again. Do not move +the tag, delete the release, or unpublish npm as a retry. A defective public +package requires a reviewed correction and a new version. diff --git a/docs/roadmap.md b/docs/roadmap.md index a5f86686c..cd78ee103 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -20,6 +20,30 @@ validation, provider prerequisites, target identity, cancellation, and owned resource cleanup remain part of each tool's contract. Setup still discloses installation changes and requires approval before writing or installing. +## Planned work + +The [website guides](https://morluto.github.io/rea/guides/) and +[generated catalog](product-catalog.json) describe available workflows. +Current development priorities are: + +- Keep generated metadata and narrative documentation aligned with tools, + providers, setup options and releases. +- Expand native architecture, type and indirect-call verification across + Hopper and Ghidra. +- Connect additional static extractors and runtime observations to + cross-layer feature traces. +- Improve obfuscated .NET comparisons and links between managed findings + and verified native analysis. +- Extend process, protocol, filesystem, reconnect and build-comparison + coverage, plus browser and Electron scenario actions. +- Evaluate native runtime observation through LLDB, Frida, system logs and + API tracing, and additional tools and targets such as Binary Ninja, Rizin, + LIEF, Windows-native workflows, mobile applications and firmware. + +Provider additions and platform support require the corresponding real +verification lanes; see [testing](testing.md) and +[provider evaluation](provider-evaluation.md). + ## Remaining evidence and provider work The [platform roadmap](https://github.com/morluto/rea/issues/32), @@ -62,7 +86,7 @@ Mach-O cross-target fixtures. See [testing](testing.md) for the prerequisites and exact commands. Experimental Windows x64 P0 now supplies bundled Job Object ownership, -protected runtime DACLs, and handle-based admission for native x86-64 PE +protected runtime DACLs, and handle-based admission for native x86 and x86-64 PE applications on local NTFS. Real ordinary-user CLI/MCP verification covers all 25 read-only operations and cleanup. The [Windows P0 guide](windows-ghidra-p0.md) describes this boundary and unverified broader coverage. diff --git a/docs/testing.md b/docs/testing.md index eefce750a..c82aeebab 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -6,10 +6,27 @@ from real captured inputs. Keep focused module tests for distinct failure or semantic cases that these workflows cannot reliably reproduce. A test's path or suite name does not establish its behavioral depth. +Avoid tests that mirror getters, enum mappings, fixture helpers, or prescribed +call sequences. Prefer one representative workflow over a Cartesian matrix +when every row crosses the same boundary. Keep distinct command handlers, +producer representations, failure reasons, and lifecycle states covered. +Consolidate their shared setup without hiding the inputs or expected evidence. + Tests are grouped into Vitest projects so ownership, allowed dependencies and -runtime cost are visible from their paths. Before pruning a test, identify the -replacement scenario and its assertions; passing success journeys do not -replace malformed input, cancellation, permission or cleanup coverage. +runtime cost are visible from their paths. Before pruning a distinct boundary +regression, identify the replacement scenario and its assertions; passing +success journeys do not replace malformed input, cancellation, permission or +cleanup coverage. Trivial helper checks do not need a replacement. If only +tests consume a production module, trace CLI/MCP and verifier imports, +including imports of compiled files, and remove abandoned scaffolding with its +tests when it has no runtime consumer. + +Measure slow files before removing capacity regressions. Optimize repeated +process startup, fixture construction, and production algorithms when they +dominate runtime. Keep at least one input beyond each formerly failing size or +depth; smaller fixtures do not establish that truncation or stack exhaustion +has been fixed. Record the toolchain, coverage mode, and cache conditions when +comparing durations. ## Behavioral depths @@ -103,6 +120,15 @@ names remain unchanged. The existing Apple CI job also runs the host Swift-demangling CLI/MCP regression suites, including option-like symbols, carriage returns and multiline rejection. +Process semantics/tests live in `src/domain/process/`; PTY capture implementation +and forked helper tests live in `src/process/capture/`. Evidence projection and +file workflows live in `src/application/process/`; producer-backed Evidence/host +cases run in the serial `tests/boundary/process/` lane. Installed-package probes +load the compiled capture capability owner, including the missing-optional-module +case. Real capture tests preserve actual descendant and cleanup checks. +The existing Apple job also exercises the relocated filesystem snapshot identity, +cancellation and descriptor cleanup regressions on macOS. + 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 @@ -171,7 +197,7 @@ prove behavior on the verified Ghidra 12.1.4 and JDK 21 build. | `npm run verify:ghidra:switch` | Linux x64 ELF; GCC/Clang optimized and stripped switch fixtures | GCC, Clang, GNU nm/objdump/strip, Ghidra 12.1.4, and full JDK 21 | | `npm run verify:ghidra:aarch64-jump-table` | Any supported Ghidra host; AArch64 ELF; byte/halfword tables; host ARM64 Mach-O | Clang with AArch64 target support, Ghidra 12.1.4, and full JDK 21 | | `npm run verify:ghidra:cross-format` | Any supported Ghidra host; also analyzes AArch64 ELF, x86-64 PE, and x86-64 Mach-O | `clang`, LLD, and `lld-link` in addition to host-lane prerequisites | -| `npm run verify:ghidra:windows` | Controlled Windows x64 with native x86-64 PE | Ghidra 12.1.4, full JDK 21, and the matching native artifact | +| `npm run verify:ghidra:windows` | Controlled Windows x64 with native x86-64 PE; `-- --x86` selects native x86 PE | Ghidra 12.1.4, full JDK 21, and the matching native artifact | Windows native conformance runs with `npm run verify:windows-native` and does not require Ghidra or Java. An optional independently compiled Windows fixture @@ -563,6 +589,18 @@ conditional `real-web-source-map` CI job supplies Chrome and an isolated pinned fixture compiler; static/unit checks do not acquire a browser. See [the source location guide](web-source-location.md) for the verified decoder profile. +### JavaScript large-output lane + +`npm run verify:javascript:output` exercises the CLI JSON result surface beyond +the running Node engine's single-string limit. Shared input leaves keep the +fixture's graph small; the verifier writes one temporary output file, checks its +complete byte count and an independent digest, then removes it. It requires only +Node and the built REA runtime, with space for the output plus a 1 GiB reserve. +Run `npm run verify:javascript:output -- jsonl` for compact JSONL coverage. This +opt-in lane is separate from routine tests and the canonical hash check +`npm run verify:javascript:digests`. It verifies serialization rather than an +arbitrary third-party application's parsing cost or MCP client capacity. + ### Website runtime attribution lane `npm run verify:browser:runtime` uses caller-supplied @@ -578,3 +616,26 @@ same checks through an isolated installed package. The conditional compiler. Ordinary unit/static gates acquire no browser. See [website runtime attribution](web-runtime.md) for effects, resource bounds and coverage limits. + +## Agent evaluation and conformance records + +Evaluate native, JavaScript, managed and browser investigation tasks through a +real local Codex CLI with: + +```bash +npm run verify:agent +``` + +Its report records tool selection, repeated calls, token use, completion quality, +and handling of permissions and unknowns. + +Regenerate the managed conformance manifest and Evidence completion ledger from +live verification results, or check them for drift: + +```bash +npm run evidence:generate +npm run evidence:check +``` + +The records preserve unsupported and unverified coverage as explicit unknowns. +Run the matching real-tool prerequisites described in this guide. diff --git a/docs/user-visible-errors.csv b/docs/user-visible-errors.csv index 1fa26521d..1adbcbbbc 100644 --- a/docs/user-visible-errors.csv +++ b/docs/user-visible-errors.csv @@ -56,7 +56,7 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement 46,src/application/Uninstall.ts:removeManagedPath symlink,Managed cache/state/skill path is a symbolic link,Refused to follow symlink {path}.,Exposes local path and does not explain manual safety check,"REA did not remove this item because its managed path is a symbolic link. Verify the link target before removing it manually.",implemented,focused assertion passed 47,src/application/Uninstall.ts:removeManagedPath failure,Managed item removal fails,{path} could not be removed.,Exposes local path and gives no recovery,"This item could not be removed. Check file permissions, then rerun uninstall.",implemented,verified by injected managed-path removal failure test 48,src/application/DoctorDiagnostics.ts:node,Runtime version is outside supported ranges,"Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",Low risk; exact dependency recovery,Keep current copy,unchanged,verified by doctor unit test -49,src/application/Doctor.ts:host,Host OS/distribution is unsupported,"REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux.",Low risk; clearly states supported choices,Keep current copy,unchanged,verified by doctor unit tests +49,src/application/Doctor.ts:host,Host OS/distribution is unsupported,"REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and CachyOS.",Low risk; clearly states supported choices,Keep current copy,unchanged,verified by doctor unit tests 50,src/application/Doctor.ts:analysis engine,Optional deep-analysis executable cannot be found,"Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH.",Low risk in setup-specific diagnostic; exact recovery paths,Keep current copy,unchanged,verified by doctor unit tests 51,src/application/Doctor.ts:target,Optional target path fails shared validation,Supply a readable local app or program path.,Low risk; tells caller valid target class,Keep current copy,unchanged,verified by doctor unit test 52,src/application/Setup.ts:approval,Setup plan lacks approval,"Review the setup plan, then rerun interactively or with --yes.",Low risk; exact authorization recovery,Keep current copy,unchanged,verified by setup unit tests @@ -67,13 +67,13 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement 57,src/application/Setup.ts:unsupported platform,Platform is neither macOS nor Linux,REA supports Hopper on macOS and selected 64-bit Linux distributions.,Low risk; states support boundary,Keep current copy,unchanged,verified by setup unit test 58,src/application/SetupHost.ts:unsupported Node,Runtime version cannot run setup,"Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ and rerun setup.",Low risk; exact dependency and retry,Keep current copy,unchanged,verified by setup host tests 59,src/application/Setup.ts:unsupported macOS,macOS version is missing or older than 12,Upgrade to macOS 12 or newer.,Low risk; exact support floor,Keep current copy,unchanged,verified by setup host tests -60,src/application/Setup.ts:unsupported Linux,Linux distribution is outside supported set,"REA supports Hopper on Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test -61,src/application/ProcessCli.ts:readJson read,Process scenario or evidence file cannot be read,Raw filesystem exception through CLI framework,Can expose local paths/codes and no recovery,"Process input file could not be read. Check that the path exists and is readable.",implemented,verified by focused CLI test -62,src/application/ProcessCli.ts:readJson size,Process scenario or evidence file exceeds 64 MiB,Process JSON input exceeds byte limit,Does not state limit or recovery,"Process input file is too large. Reduce it below 64 MiB, then try again.",implemented,verified by focused sparse-file CLI test -63,src/application/ProcessCli.ts:readJson parse,Process scenario or evidence file is malformed JSON,Raw JSON parser exception through CLI framework,Can expose parser implementation and lacks recovery,"Process input file is not valid JSON. Repair the file, then try again.",implemented,verified by focused CLI test -64,src/application/ProcessCli.ts:capture scenario parse,Scenario fails Process Capture schema,Raw Zod validation exception through CLI framework,Exposes schema internals and field paths without stable recovery,"Process scenario is invalid. Check its required fields and limits, then try again.",implemented,verified by focused CLI test -66,src/application/ProcessCli.ts:wrong evidence,Comparison receives valid Evidence from another workflow,Expected REA process capture Evidence,Uses internal schema name and does not explain recovery,"Capture evidence is not from the current process-capture workflow. Create new capture evidence, then try again.",implemented,verified by focused CLI test -67,src/application/ProcessCli.ts:unexpected,Unexpected parser/comparison/canonicalization failure escapes expected classifications,Raw thrown exception through CLI framework,Can expose stack or internal invariant,"Process command could not complete. Check the input files and run `rea doctor`, then try again.",implemented,verified by direct unexpected-cause projection test +60,src/application/Setup.ts:unsupported Linux,Linux distribution is outside supported set,"REA supports Hopper on Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and CachyOS.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test +61,src/application/process/ProcessCli.ts:readJson read,Process scenario or evidence file cannot be read,Raw filesystem exception through CLI framework,Can expose local paths/codes and no recovery,"Process input file could not be read. Check that the path exists and is readable.",implemented,verified by focused CLI test +62,src/application/process/ProcessCli.ts:readJson size,Process scenario or evidence file exceeds 64 MiB,Process JSON input exceeds byte limit,Does not state limit or recovery,"Process input file is too large. Reduce it below 64 MiB, then try again.",implemented,verified by focused sparse-file CLI test +63,src/application/process/ProcessCli.ts:readJson parse,Process scenario or evidence file is malformed JSON,Raw JSON parser exception through CLI framework,Can expose parser implementation and lacks recovery,"Process input file is not valid JSON. Repair the file, then try again.",implemented,verified by focused CLI test +64,src/application/process/ProcessCli.ts:capture scenario parse,Scenario fails Process Capture schema,Raw Zod validation exception through CLI framework,Exposes schema internals and field paths without stable recovery,"Process scenario is invalid. Check its required fields and limits, then try again.",implemented,verified by focused CLI test +66,src/application/process/ProcessCli.ts:wrong evidence,Comparison receives valid Evidence from another workflow,Expected REA process capture Evidence,Uses internal schema name and does not explain recovery,"Capture evidence is not from the current process-capture workflow. Create new capture evidence, then try again.",implemented,verified by focused CLI test +67,src/application/process/ProcessCli.ts:unexpected,Unexpected parser/comparison/canonicalization failure escapes expected classifications,Raw thrown exception through CLI framework,Can expose stack or internal invariant,"Process command could not complete. Check the input files and run `rea doctor`, then try again.",implemented,verified by direct unexpected-cause projection test 68,src/server/registerEvidenceTools.ts:object guard,Validated provider-tool input is unexpectedly not an object,Provider tool input was not an object,Exposes provider architecture and implies caller can fix impossible state,Remove the impossible duplicate guard and parse through the declared JSON-object schema.,removed,verified by typecheck and MCP integration tests 69,src/server/registerEnhancedTools.ts:object guard,Validated enhanced-tool input is unexpectedly not an object,Validated enhanced input was not a JSON object,Exposes validation/JSON implementation detail,Remove the impossible duplicate guard and parse through the declared JSON-object schema.,removed,verified by typecheck and enhanced MCP integration tests 70,src/server/registerOfficialTools.ts:object guard,Validated direct-tool input is unexpectedly not an object,Validated MCP tool input was not a JSON object,Exposes MCP validation implementation detail,Remove the impossible duplicate guard and parse through the declared JSON-object schema.,removed,verified by typecheck and official MCP integration tests diff --git a/docs/verification/managed-conformance-manifest.json b/docs/verification/managed-conformance-manifest.json index b0e53920e..3092caf24 100644 --- a/docs/verification/managed-conformance-manifest.json +++ b/docs/verification/managed-conformance-manifest.json @@ -1,5 +1,5 @@ { - "manifest_id": "ecm_10055d87f3f4e2ad1f03c6f18ec8dc985f38380d75f8dfafd5eafd8065be3e9c", + "manifest_id": "ecm_d5fdca7e469b71989b98eaec30d26af3ccb3a08b42d558b4d4fe3b1d220c3898", "verifier": { "id": "rea-managed-conformance", "version": "1" @@ -13,7 +13,7 @@ "skill_digests": [ { "skill_id": "reverse-engineer-anything", - "sha256": "cff984f17cfec05d62e1d25a26ce9ec44a9473947c81a49278fa4efdc519ddf7" + "sha256": "3f8e02d3dc409f4fe0ac3a4315cfa4c34de53e504f627b6dc9cf6cb7b0adbedc" } ], "claims": [ diff --git a/docs/web-network-captures.md b/docs/web-network-captures.md new file mode 100644 index 000000000..5216c2031 --- /dev/null +++ b/docs/web-network-captures.md @@ -0,0 +1,147 @@ +# Historical web network captures + +Use `inspect_web_network_capture` / `inspect-web-network-capture` to inspect +retained producer evidence. This is an offline operation: recorded URLs are +never fetched, and no browser or proxy listener is started. + +```sh +rea inspect-web-network-capture /absolute/session.har har --json +rea inspect-web-network-capture /absolute/session.har har --record 2 --record 0 --json +``` + +```json +{ + "name": "inspect_web_network_capture", + "arguments": { + "capture_path": "/absolute/session.har", + "format": "har", + "record_ordinals": [2, 0] + } +} +``` + +Omit ordinals to return every record. Explicit selection preserves caller order +and original zero-based producer ordinals; duplicate or out-of-range selections +are input errors. Every record is validated before selection, so selecting one +entry does not hide a malformed entry elsewhere. + +## Formats and upstream profiles + +HAR uses unchanged `har-schema@2.0.0` draft-06 schemas, `ajv@8.20.0`, +`ajv-formats@3.0.1`, `jsonc-parser@3.3.1` JSON visitor, and +`lossless-json@4.3.1` numeric representations, pinned in the npm lockfile. +Only the HAR 1.2 profile is supported. Unknown extensions remain reported +evidence. Duplicate object keys, including equal values and escaped spellings, +are rejected before materialization. A `__proto__` member returns an explicit +unsupported-boundary error in both formats: the current shared JSON schema +cannot preserve this member. Original capture files remain unchanged. +The observed mitmproxy 12.2.3 `SaveHar` profile can emit `postData.text: null` +for a missing request body. REA preserves that reported null, omitting the +optional field only from its validation copy for this exact producer profile; +it does not invent an empty string or body bytes. + +Native mitmproxy decoding requires a caller-supplied **mitmdump 12.2.3 on +Linux**. Set its absolute executable path: + +```sh +REA_MITMDUMP_COMMAND=/absolute/mitmdump rea inspect-web-network-capture /absolute/session.mitm mitmproxy --json +``` + +REA does not install mitmproxy. Its owned process uses a private configuration +directory, `--no-server`, and the unchanged upstream `mitmproxy.io.tnetstring` +decoder. It does not run `FlowReader` migrations or convert native states to +HAR. Raw IDs, state versions, flow types, backup states and unknown extensions +are preserved without synthesizing connections, timestamps or content. The +tested upstream source is tag `v12.2.3`, commit +`6c09d56e4c29a92f5ad01b03199977584b8ea14f`; other native tool versions return +an explicit unsupported-profile diagnostic. Non-Linux native decoding is not +supported by this adapter. + +## Reading the evidence + +- `artifact` identifies the caller-selected file and stable snapshot digest. +- `records` preserve producer order/ordinals and original JSON pointers or byte + ranges. No live browser transaction or scenario IDs are invented. +- `reported` retains the producer's fields. HAR container metadata is returned + separately, with `log.entries` replaced by `null` and `records_pointer` + identifying the record collection. This is an inspection representation, + not a reconstructed HAR file. +- `numeric_literals` preserve HAR number lexemes and native integer/float + representations, including unsafe integers and non-finite native floats. + Values that cannot be safely represented as JSON numbers are `null` in the + reported view. Unsafe standard HAR numeric fields fail schema validation; + unsafe extension numbers remain available through their sidecars. +- `binary_fields` retain exposed bytes, base64 and SHA-256 independently of + declared sizes. Valid HAR `content.encoding: "base64"` is decoded exactly; + producer-decoded Unicode text, missing text and unknown encodings do not + establish original body bytes. Post-data parameters do not reconstruct a + multipart body. Native byte fields also have an exact UTF-8 display view + when valid; binary sidecars establish their actual representation. Native + missing content and empty content remain distinct. +- `redactions` identify known transport authentication exclusions and explicit + sensitive-value exclusions. Redacted byte fields retain neither original + bytes nor their digest. Unknown extension names and token-looking query + parameters do not establish sensitivity. + +Mark sensitive literal string/UTF-8 byte values with MCP `sensitive_values`, or +repeat CLI `--sensitive-value`. These declarations are never persisted in +Evidence parameters; the count is retained. Explicit text/UTF-8 byte matches +exclude the entire field with a reported `null` rather than substitute a text +marker or rewrite payload bytes. This prevents replacement markers from +reintroducing declared literals. The file digest identifies the original artifact +unless it matches an explicit declaration; in that case `artifact.sha256` and +Evidence `subject` are null with an explanation. A sensitive property name excludes that +entire property and its sidecars; a `scope: "property-name"` redaction points +to its actual parent. No substitute property name or child pointer is invented. +Omitted subtrees are still validated, including canonical HAR base64. Explicitly sensitive artifact +paths are empty in Evidence and omitted from locations, while the original +SHA-256 and size remain available unless the digest itself is explicitly excluded. Failure messages, diagnostics and cleanup +resources follow the same explicit declarations without changing error types; +sensitive issue pointers identify a real ancestor instead of a fabricated +coordinate. Header/cookie authentication values and known +transport URL userinfo are excluded structurally, including native backups. + +Generated free-text limitations follow the same literal declarations, including +global, per-record and Evidence explanations added during projection. Contract +field names, typed discriminators and exact decoder identities keep their +canonical meanings; declarations do not rename formats or providers. + +Historical evidence does not prove runtime attribution, execution or deployment +authenticity. Mitmproxy messages are upstream-assembled messages, not recovered +wire fragments. Native dictionary duplicate keys have the unchanged upstream +decoder's last-value semantics; this adapter does not claim to recover discarded +duplicates or the original numeric spelling of parsed native floats. + +## Resource and lifecycle boundaries + +Complete evidence has a 32 MiB input, 96 MiB decoder reply and 64-level nesting +budget. Exceeding a budget returns no partial success. +Oversized selected captures return an input error identifying `capture_path` +and the byte budget; owned reply/output failures remain output or provider errors. +HAR runs in an owned +192 MiB Node old-generation heap with one V8 worker. Native decoding applies +Linux limits of 768 MiB address space, 30 seconds CPU and 96 MiB file output; +both commands have a 30-second wall deadline and independently supervised +process cleanup. Owned decoder diagnostics retain at most 1 MiB across stdout +and stderr; the collector drops overflow as it arrives, reports the limit +failure and verifies cleanup. Heap limits alone do not establish aggregate RSS limits. + +Stable regular-file reads reject symlinks, replacement and concurrent changes. +Private snapshots, declarations and tool configuration are removed before the +result returns. Cleanup uncertainty retains the selected capture, resource and +original failure information. The original capture is never changed. + +## Excluded coordinates and read failures + +Explicitly marked values also exclude matching source coordinates and their sidecars. A hidden record coordinate is reported as `location.kind: "unknown"`; REA does not invent a replacement pointer. Original record ordinals and artifact digests remain available. + +Host filesystem read denials return `code: "access_denied"`, `category: "unavailable"`, the actual `EACCES`/`EPERM` code and read-access guidance. Missing files remain input failures. Cleanup failures retain the prior structured error, including its input constraints or provider diagnostics, with explicitly marked text excluded. + +An observed replacement or in-place change during stable artifact acquisition +returns `code: "artifact_changed"`, `category: "integrity_mismatch"` and +`retryable: true`, retaining the selected path and reader's reason. Wait until +the capture file is stable before retrying. No unstable snapshot is decoded. +Command cleanup failures retain the original status and both diagnostic streams; +the decoder's ownership preparation receives the same cancellation signal. + +MCP advertises the same strict named input contract used by the application. Historical inspection delegates argument validation to that shared application boundary so accepted explicit-sensitive declarations also cover invalid argument names and other correction details. The SDK receives raw arguments through its Standard Schema interface; malformed inputs still fail before decoder or filesystem effects and return the normal structured REA error. Protocol-envelope validation remains owned by MCP. diff --git a/docs/windows-ghidra-p0.md b/docs/windows-ghidra-p0.md index f837bae09..ec54f5908 100644 --- a/docs/windows-ghidra-p0.md +++ b/docs/windows-ghidra-p0.md @@ -3,6 +3,8 @@ This guide describes repository main. npm 4.1.0 includes the Windows native bundle; check the [release boundary](installation.md#released-package-and-main) before applying these instructions to an older published package. +Native x86 (32-bit) PE support is available on repository main; npm releases +through 5.0.0 admit x86-64 PE targets on Windows. Status: experimental Windows x64 support for the read-only P0 boundary. Windows package builds bundle native process, filesystem, and DACL controls. REA @@ -15,7 +17,7 @@ additional permission flag or degraded mode is required. - Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+; - an operator-installed Ghidra 12.1.x distribution (verified with 12.1.4); - a 64-bit full JDK inside that installation's declared Java range (JDK 21 or newer, with no maximum, for current 12.1 releases); -- an explicit native, non-managed, non-DLL x86-64 PE application; and +- an explicit native, non-managed, non-DLL x86 or x86-64 PE application; and - the 25 read-only Ghidra inventory, memory, and function-analysis operations. Loaded memory reads and file offsets preserve Ghidra's observed source mappings. @@ -98,7 +100,7 @@ and artifact SHA-256 before loading the package-owned addon. Native failure reasons remain distinct from a valid installation and an unsupported target. PE header classification and SHA-256 come from the same open file. The Windows -provider admits native x86-64 PE applications and rejects unsupported roles, +provider admits native x86 and x86-64 PE applications and rejects unsupported roles, architectures, managed images, and malformed headers. The original selected source coordinate is preserved for native admission. @@ -156,6 +158,8 @@ artifact and manifest. ```powershell npm run verify:windows-native npm run verify:ghidra:windows +npm run verify:ghidra:windows -- --x86 +npm run verify:ghidra:windows:package -- --x86 npm run verify:ghidra:windows:package -- C:\fixtures\installed-rea C:\fixtures\sample.exe ``` diff --git a/install.sh b/install.sh index 2df6548e2..c236fc8ab 100755 --- a/install.sh +++ b/install.sh @@ -3,6 +3,10 @@ set -euo pipefail PACKAGE="rea-agents" REPOSITORY="morluto/rea" +numeric_identifier='(0|[1-9][0-9]*)' +prerelease_identifier="(${numeric_identifier}|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)" +build_identifier='[0-9A-Za-z-]+' +version_pattern="${numeric_identifier}\\.${numeric_identifier}\\.${numeric_identifier}(-${prerelease_identifier}(\\.${prerelease_identifier})*)?(\\+${build_identifier}(\\.${build_identifier})*)?" version="${REA_VERSION:-}" dry_run=false start_setup=true @@ -67,14 +71,20 @@ if ! [[ "$_node_patch" =~ ^[0-9]+(\+[0-9A-Za-z.-]+)?$ ]] || fi if [[ -n "$version" ]]; then - [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || fail "version must be an exact semantic version." + [[ "$version" =~ ^${version_pattern}$ ]] || fail "version must be an exact semantic version." else release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "the latest REA release could not be resolved. Check network access or pass --version VERSION, then retry." tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})' 2>/dev/null)" || fail "the release response was invalid. Retry later or pass --version VERSION." - [[ "$tag" =~ ^rea-agents-([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION." + [[ "$tag" =~ ^rea-agents-(${version_pattern})$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION." version="${BASH_REMATCH[1]}" fi +# npm's exact-version resolver cleans build metadata from the requested version, +# so it may install the base release and make the post-install identity check fail. +if [[ "$version" =~ \+ ]]; then + fail "npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry." +fi + prefix_args=() if [[ "$platform" == "Linux" ]]; then prefix_args=(--prefix "$HOME/.local") diff --git a/native/windows/src/filesystem.cc b/native/windows/src/filesystem.cc index d50fc1e29..8d7ef7a6b 100644 --- a/native/windows/src/filesystem.cc +++ b/native/windows/src/filesystem.cc @@ -277,14 +277,15 @@ static void directory(Runtime& root, const std::wstring& relative) { } } -static Handle createPrivateFile(Runtime& root, const std::wstring& relative) { +static Handle createPrivateFile(Runtime& root, const std::wstring& relative, + DWORD access = DELETE | GENERIC_WRITE | GENERIC_READ | READ_CONTROL) { const auto path = relativePath(root, relative); const auto parentPath = path.substr(0, path.find_last_of(L'\\')); auto parent = runtimeDirectory(root, parentPath); verifyPrivate(parent->get(), parentPath, parentPath == root.path); Security security; auto file = createRelative(parent->get(), path.substr(path.find_last_of(L'\\') + 1), - GENERIC_WRITE | GENERIC_READ | READ_CONTROL, false, security, path); + access, false, security, path); verifyPrivate(file.get(), path, false); return file; } @@ -305,6 +306,26 @@ static void dispose(HANDLE handle, const std::wstring& path) { require(SetFileInformationByHandle(handle, FileDispositionInfo, &deletion, sizeof(deletion)), "Delete owned object by handle failed", path); } +static void disposeSnapshot(Runtime& root, Handle& file, const std::wstring& path) { + // Snapshot readers do not share DELETE access. Release the writer only for + // failed-copy cleanup, with its parent pinned and its identity recorded. + auto parent = runtimeDirectory(root, path.substr(0, path.find_last_of(L'\\'))); + FILE_ID_INFO expected{}; + require(GetFileInformationByHandleEx(file.get(), FileIdInfo, &expected, sizeof(expected)), + "Read incomplete snapshot identity failed", path); + file.reset(); + auto cleanup = openComponent(path, DELETE | GENERIC_READ | FILE_WRITE_ATTRIBUTES, false); + FILE_ID_INFO observed{}; + require(GetFileInformationByHandleEx(cleanup.get(), FileIdInfo, &observed, sizeof(observed)), + "Read snapshot cleanup identity failed", path); + require(expected.VolumeSerialNumber == observed.VolumeSerialNumber && + std::equal(std::begin(expected.FileId.Identifier), std::end(expected.FileId.Identifier), + std::begin(observed.FileId.Identifier)), + "Incomplete snapshot identity changed before cleanup", path, ERROR_ACCESS_DENIED); + verifyPrivate(cleanup.get(), path, false); + dispose(cleanup.get(), path); +} + void closeRuntime(Runtime& root) { if (root.closed) return; // Windows keeps the bearer descriptor and snapshot leases until this owner @@ -412,6 +433,17 @@ static void copySnapshot(napi_env, void* pointer) { static void completeSnapshot(napi_env env, napi_status status, void* pointer) { std::unique_ptr work(static_cast(pointer)); work->root->snapshotPending = false; + const auto reject = [&](const Failure& failure) { + try { + disposeSnapshot(*work->root, work->destination, work->path); + napi_reject_deferred(env, work->deferred, failureValue(env, failure)); + } catch (const Failure& cleanupFailure) { + const Failure combined( + failure.constraint + "; incomplete snapshot cleanup failed: " + cleanupFailure.constraint, + work->path, cleanupFailure.win32); + napi_reject_deferred(env, work->deferred, failureValue(env, combined)); + } + }; try { if (work->failure) throw *work->failure; require(status == napi_ok && !work->root->snapshotCancelled.load(), "Native snapshot worker was cancelled", work->path, ERROR_OPERATION_ABORTED); @@ -422,10 +454,9 @@ static void completeSnapshot(napi_env env, napi_status status, void* pointer) { work->root->immutableFiles.push_back(std::move(work->destination)); check(napi_resolve_deferred(env, work->deferred, result)); } catch (const Failure& failure) { - napi_reject_deferred(env, work->deferred, failureValue(env, failure)); + reject(failure); } catch (const std::exception& failure) { - napi_reject_deferred(env, work->deferred, - failureValue(env, Failure(failure.what(), work->path, ERROR_GEN_FAILURE))); + reject(Failure(failure.what(), work->path, ERROR_GEN_FAILURE)); } napi_delete_reference(env, work->rootReference); napi_delete_async_work(env, work->work); @@ -436,7 +467,9 @@ static napi_value snapshot(napi_env env, napi_value rootValue, Runtime& root, auto work = std::make_unique(env, root); work->source = openFile(sourcePath); work->path = relativePath(root, relative); - work->destination = createPrivateFile(root, relative); + // Retain the write lease with read-only sharing, but omit DELETE authority: + // Java's RandomAccessFile reader shares read/write, not delete access. + work->destination = createPrivateFile(root, relative, GENERIC_WRITE | GENERIC_READ | READ_CONTROL); napi_value promise; check(napi_create_promise(env, &work->deferred, &promise)); check(napi_create_reference(env, rootValue, 1, &work->rootReference)); @@ -506,7 +539,13 @@ napi_value filesystemCall(napi_env env, const std::wstring& operation, const std auto file = runtimeDirectory(root, path.substr(0, path.find_last_of(L'\\'))); file->path = path; file->requestedPath = path; - file->handles.push_back(openComponent(path, GENERIC_READ, false)); + // Retained output handles have write and DELETE access, so this read open + // must share both to pass Windows' symmetric share check. Those retained + // handles still omit FILE_SHARE_WRITE and FILE_SHARE_DELETE, blocking + // competing writes and delete opens. + file->handles.push_back(openComponent(path, GENERIC_READ, false, + FILE_SHARE_READ | FILE_SHARE_WRITE | + FILE_SHARE_DELETE)); verifyPrivate(file->get(), path, false); auto result = identity(env, file->get(), path); set(env, result, "handle", wrap(env, std::move(file))); diff --git a/package-lock.json b/package-lock.json index f1c2520dc..e47340616 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rea-agents", - "version": "4.1.0", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rea-agents", - "version": "4.1.0", + "version": "5.0.0", "license": "MIT", "dependencies": { "@babel/parser": "8.0.4", @@ -19,14 +19,18 @@ "@modelcontextprotocol/server": "2.3.1", "@xterm/addon-serialize": "0.14.0", "@xterm/headless": "6.0.0", - "@zip.js/zip.js": "2.8.26", + "@zip.js/zip.js": "2.15.0", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", "canonicalize": "3.0.0", "cross-spawn": "7.0.6", "diff": "9.0.0", + "har-schema": "2.0.0", "ignore": "7.0.6", "incur": "0.6.1", "isomorphic-git": "1.38.7", "jsonc-parser": "3.3.1", + "lossless-json": "4.3.1", "pino": "10.4.0", "playwright-core": "1.58.2", "plist": "5.0.0", @@ -48,7 +52,6 @@ "@types/write-file-atomic": "4.0.3", "@types/ws": "8.18.1", "@vitest/coverage-v8": "5.0.3", - "ajv": "8.20.0", "husky": "9.1.7", "jscpd": "5.0.12", "knip": "6.39.0", @@ -3945,9 +3948,9 @@ ] }, "node_modules/@zip.js/zip.js": { - "version": "2.8.26", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.26.tgz", - "integrity": "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.15.0.tgz", + "integrity": "sha512-hYAuHAaWjt0axbofaDL5XUlmrQPsBDcK3f45ApZuh6N+8UGVk26GoGdtGDvcvzahsHytTQ6DtBVXwG9IcCMjjQ==", "license": "BSD-3-Clause", "engines": { "bun": ">=0.7.0", @@ -3971,7 +3974,6 @@ "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3984,6 +3986,23 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/algoliasearch": { "version": "5.59.0", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.59.0.tgz", @@ -4660,7 +4679,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-string-truncated-width": { @@ -4682,7 +4700,6 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", - "dev": true, "funding": [ { "type": "github", @@ -4890,6 +4907,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -5315,7 +5341,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, "license": "MIT" }, "node_modules/jsonc-parser": { @@ -5743,6 +5768,12 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/lossless-json": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.3.1.tgz", + "integrity": "sha512-SqD/Bg3ZfltBJ2Z14hJ/BihnvtV553WO4g9/ePtlp4lrnl9jF3AdIJt53A/Wkg/0Li+LMfxaBqgx1MiFZdQlpQ==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.5.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", @@ -6609,7 +6640,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 87e673652..ebf50401b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rea-agents", "mcpName": "io.github.morluto/rea", - "version": "4.1.0", + "version": "5.0.0", "description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.", "license": "MIT", "repository": { @@ -45,10 +45,13 @@ "bridge/native/NativeUIChildren.swift", "bridge/native/ReaNativeUI.swift", "bridge/native/rea_lldb_tracer.py", + "bridge/process/ProcessRunTokenReader.swift", + "bridge/process/ProcessRunTokenReaderMain.swift", "bridge/ghidra/ReaGhidraBridge.java", "bridge/ghidra/ReaGhidraExtensions.java", "bridge/ghidra/ReaGhidraPrepareCom.java", "bridge/android/ReaJadxBridge.java", + "bridge/mitmproxy/capture.py", "scripts/rea.mjs", "scripts/electron-active-hook.cjs", "scripts/electron-active-hook-boundaries.cjs", @@ -67,11 +70,11 @@ "metadata:generate": "node scripts/generate-package-metadata.mjs", "metadata:check": "node scripts/generate-package-metadata.mjs --check", "mcp-registry:check": "node scripts/verify-mcp-registry-metadata.mjs", - "mcp-catalog:generate": "npm run build:cached && node scripts/generate-mcp-tool-catalog.mjs", + "mcp-catalog:generate": "npm run build:cached", "mcp-catalog:check": "node scripts/generate-mcp-tool-catalog.mjs --check", "prebuild": "npm run deps:check && npm run metadata:check", "build": "node scripts/run-exclusive.mjs build npm run build:unlocked", - "build:unlocked": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json", + "build:unlocked": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/generate-mcp-tool-catalog.mjs", "precheck": "npm run deps:check", "check": "turbo run typecheck lint format:check knip metadata:check", "precheck:fast": "npm run deps:check", @@ -84,7 +87,7 @@ "check:pr": "turbo run typecheck lint format:check knip metadata:check test:run && npm run docs:check && npm run jscpd && npm run scan:todos", "precheck:ci": "npm run deps:check", "check:ci": "turbo run typecheck lint format:check knip metadata:check jscpd scan:todos", - "build:cached": "turbo run build", + "build:cached": "node scripts/check-dependency-install.mjs && turbo run build", "format": "oxfmt .", "format:check": "oxfmt --check .", "lint": "oxlint && npm run verify:module-boundaries", @@ -142,6 +145,7 @@ "verify:inspector": "npm run build:cached && node scripts/verify/inspector/runtime-observation.mjs", "verify:javascript-runtime": "npm run build:cached && node scripts/verify/javascript/runtime-observation.mjs", "verify:javascript:digests": "npm run build:cached && node scripts/verify/javascript/digests.mjs", + "verify:javascript:output": "npm run build:cached && node scripts/verify/javascript/output.mjs", "verify:application-workflows": "npm run build:cached && node scripts/verify/javascript/application-workflows.mjs", "verify:managed": "npm run build:cached && node scripts/verify/managed/conformance.mjs", "verify:firmware": "npm run build:cached && node scripts/verify/firmware/analysis.mjs", @@ -166,7 +170,7 @@ "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs", "docs:generate": "node scripts/run-exclusive.mjs docs npm run docs:generate:unlocked", - "docs:generate:unlocked": "npm run metadata:generate && npm run build:cached && node scripts/generate-mcp-tool-catalog.mjs && node scripts/generate-skill-metadata.mjs && node scripts/generate-product-catalog.mjs && node scripts/generate-completion-ledger.mjs && node scripts/generate-error-schema.mjs", + "docs:generate:unlocked": "npm run metadata:generate && npm run build:cached && node scripts/generate-skill-metadata.mjs && node scripts/generate-product-catalog.mjs && node scripts/generate-completion-ledger.mjs && node scripts/generate-error-schema.mjs", "docs:check": "node scripts/run-exclusive.mjs docs turbo run docs:check:built", "docs:check:built": "node scripts/generate-package-metadata.mjs --check && node scripts/generate-mcp-tool-catalog.mjs --check && node scripts/generate-skill-metadata.mjs --check && node scripts/generate-product-catalog.mjs --check && node scripts/generate-completion-ledger.mjs --check && node scripts/check-doc-facts.mjs && node scripts/generate-error-schema.mjs --check", "docs:error-schema": "npm run build:cached && node scripts/generate-error-schema.mjs", @@ -190,7 +194,8 @@ "verify:javascript:recovery": "node scripts/verify/javascript/recovery.mjs", "verify:module-boundaries": "node scripts/verify-module-boundaries.mjs", "verify:browser:source-maps": "npm run build:cached && node scripts/verify-browser-source-maps.mjs", - "verify:browser:runtime": "npm run build:cached && node scripts/verify-browser-runtime.mjs" + "verify:browser:runtime": "npm run build:cached && node scripts/verify-browser-runtime.mjs", + "verify:web:network-captures": "npm run build:cached && node scripts/verify-web-network-captures.mjs" }, "lint-staged": { "*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [ @@ -210,14 +215,18 @@ "@modelcontextprotocol/server": "2.3.1", "@xterm/addon-serialize": "0.14.0", "@xterm/headless": "6.0.0", - "@zip.js/zip.js": "2.8.26", + "@zip.js/zip.js": "2.15.0", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", "canonicalize": "3.0.0", "cross-spawn": "7.0.6", "diff": "9.0.0", + "har-schema": "2.0.0", "ignore": "7.0.6", "incur": "0.6.1", "isomorphic-git": "1.38.7", "jsonc-parser": "3.3.1", + "lossless-json": "4.3.1", "pino": "10.4.0", "playwright-core": "1.58.2", "plist": "5.0.0", @@ -240,7 +249,6 @@ "@types/write-file-atomic": "4.0.3", "@types/ws": "8.18.1", "@vitest/coverage-v8": "5.0.3", - "ajv": "8.20.0", "husky": "9.1.7", "jscpd": "5.0.12", "knip": "6.39.0", diff --git a/release-please-config.json b/release-please-config.json index c1ac99aa0..85a7eee3b 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -16,7 +16,6 @@ ".": { "package-name": "rea-agents", "extra-files": [ - { "type": "generic", "path": "README.md" }, { "type": "generic", "path": "README_zh.md" }, { "type": "generic", "path": "README_ja.md" }, { "type": "generic", "path": "README_ko.md" }, diff --git a/scripts/create-ghidra-windows-fixture.mjs b/scripts/create-ghidra-windows-fixture.mjs index fcb1df5f2..6538ec57f 100644 --- a/scripts/create-ghidra-windows-fixture.mjs +++ b/scripts/create-ghidra-windows-fixture.mjs @@ -3,50 +3,68 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const output = resolve(root, "build", "fixtures", "rea-ghidra-windows.exe"); -const bytes = Buffer.alloc(1024); -const peOffset = 0x80; -const optionalHeader = peOffset + 24; -const sectionTable = optionalHeader + 0xf0; +for (const architecture of ["x86_64", "x86"]) { + const x86 = architecture === "x86"; + const output = resolve( + root, + "build", + "fixtures", + x86 ? "rea-ghidra-windows-x86.exe" : "rea-ghidra-windows.exe", + ); + const bytes = Buffer.alloc(1024); + const peOffset = 0x80; + const optionalHeader = peOffset + 24; + const optionalHeaderSize = x86 ? 0xe0 : 0xf0; + const sectionTable = optionalHeader + optionalHeaderSize; -bytes.write("MZ", 0, "ascii"); -bytes.writeUInt32LE(peOffset, 0x3c); -bytes.write("PE\0\0", peOffset, "binary"); -bytes.writeUInt16LE(0x8664, peOffset + 4); -bytes.writeUInt16LE(1, peOffset + 6); -bytes.writeUInt16LE(0xf0, peOffset + 20); -bytes.writeUInt16LE(0x22, peOffset + 22); + bytes.write("MZ", 0, "ascii"); + bytes.writeUInt32LE(peOffset, 0x3c); + bytes.write("PE\0\0", peOffset, "binary"); + bytes.writeUInt16LE(x86 ? 0x14c : 0x8664, peOffset + 4); + bytes.writeUInt16LE(1, peOffset + 6); + bytes.writeUInt16LE(optionalHeaderSize, peOffset + 20); + bytes.writeUInt16LE(x86 ? 0x102 : 0x22, peOffset + 22); -bytes.writeUInt16LE(0x20b, optionalHeader); -bytes.writeUInt32LE(0x200, optionalHeader + 4); -bytes.writeUInt32LE(0x1000, optionalHeader + 16); -bytes.writeUInt32LE(0x1000, optionalHeader + 20); -bytes.writeBigUInt64LE(0x1_4000_0000n, optionalHeader + 24); -bytes.writeUInt32LE(0x1000, optionalHeader + 32); -bytes.writeUInt32LE(0x200, optionalHeader + 36); -bytes.writeUInt16LE(6, optionalHeader + 40); -bytes.writeUInt16LE(6, optionalHeader + 48); -bytes.writeUInt32LE(0x2000, optionalHeader + 56); -bytes.writeUInt32LE(0x200, optionalHeader + 60); -bytes.writeUInt16LE(3, optionalHeader + 68); -bytes.writeUInt16LE(0x8160, optionalHeader + 70); -bytes.writeBigUInt64LE(0x10_0000n, optionalHeader + 72); -bytes.writeBigUInt64LE(0x1000n, optionalHeader + 80); -bytes.writeBigUInt64LE(0x10_0000n, optionalHeader + 88); -bytes.writeBigUInt64LE(0x1000n, optionalHeader + 96); -bytes.writeUInt32LE(16, optionalHeader + 108); + bytes.writeUInt16LE(x86 ? 0x10b : 0x20b, optionalHeader); + bytes.writeUInt32LE(0x200, optionalHeader + 4); + bytes.writeUInt32LE(0x1000, optionalHeader + 16); + bytes.writeUInt32LE(0x1000, optionalHeader + 20); + if (x86) bytes.writeUInt32LE(0x400000, optionalHeader + 28); + else bytes.writeBigUInt64LE(0x1_4000_0000n, optionalHeader + 24); + bytes.writeUInt32LE(0x1000, optionalHeader + 32); + bytes.writeUInt32LE(0x200, optionalHeader + 36); + bytes.writeUInt16LE(6, optionalHeader + 40); + bytes.writeUInt16LE(6, optionalHeader + 48); + bytes.writeUInt32LE(0x2000, optionalHeader + 56); + bytes.writeUInt32LE(0x200, optionalHeader + 60); + bytes.writeUInt16LE(3, optionalHeader + 68); + bytes.writeUInt16LE(x86 ? 0x8140 : 0x8160, optionalHeader + 70); + if (x86) { + bytes.writeUInt32LE(0x10_0000, optionalHeader + 72); + bytes.writeUInt32LE(0x1000, optionalHeader + 76); + bytes.writeUInt32LE(0x10_0000, optionalHeader + 80); + bytes.writeUInt32LE(0x1000, optionalHeader + 84); + bytes.writeUInt32LE(16, optionalHeader + 92); + } else { + bytes.writeBigUInt64LE(0x10_0000n, optionalHeader + 72); + bytes.writeBigUInt64LE(0x1000n, optionalHeader + 80); + bytes.writeBigUInt64LE(0x10_0000n, optionalHeader + 88); + bytes.writeBigUInt64LE(0x1000n, optionalHeader + 96); + bytes.writeUInt32LE(16, optionalHeader + 108); + } -bytes.write(".text", sectionTable, "ascii"); -bytes.writeUInt32LE(0x16, sectionTable + 8); -bytes.writeUInt32LE(0x1000, sectionTable + 12); -bytes.writeUInt32LE(0x200, sectionTable + 16); -bytes.writeUInt32LE(0x200, sectionTable + 20); -bytes.writeUInt32LE(0x6000_0020, sectionTable + 36); + bytes.write(".text", sectionTable, "ascii"); + bytes.writeUInt32LE(0x16, sectionTable + 8); + bytes.writeUInt32LE(0x1000, sectionTable + 12); + bytes.writeUInt32LE(0x200, sectionTable + 16); + bytes.writeUInt32LE(0x200, sectionTable + 20); + bytes.writeUInt32LE(0x6000_0020, sectionTable + 36); -// entry calls the second function, returns zero; second returns 42. -bytes.set([0xe8, 0x0b, 0, 0, 0, 0x31, 0xc0, 0xc3], 0x200); -bytes.set([0xb8, 0x2a, 0, 0, 0, 0xc3], 0x210); + // entry calls the second function, returns zero; second returns 42. + bytes.set([0xe8, 0x0b, 0, 0, 0, 0x31, 0xc0, 0xc3], 0x200); + bytes.set([0xb8, 0x2a, 0, 0, 0, 0xc3], 0x210); -await mkdir(dirname(output), { recursive: true }); -await writeFile(output, bytes); -process.stdout.write(`${output}\n`); + await mkdir(dirname(output), { recursive: true }); + await writeFile(output, bytes); + if (!x86) process.stdout.write(`${output}\n`); +} diff --git a/scripts/fixtures/generate-mitmproxy-capture.py b/scripts/fixtures/generate-mitmproxy-capture.py new file mode 100644 index 000000000..4be3dd4a7 --- /dev/null +++ b/scripts/fixtures/generate-mitmproxy-capture.py @@ -0,0 +1,151 @@ +import importlib.util +import json +from pathlib import Path +from mitmproxy import connection, ctx, http +from mitmproxy.io import FlowWriter +from mitmproxy.io import tnetstring +from mitmproxy.addons.savehar import SaveHar +from mitmproxy.websocket import WebSocketData, WebSocketMessage + +class Generator: + def load(self, loader): + loader.add_option("rea_fixture_root", str, "", "Owned synthetic fixture root") + loader.add_option("rea_adapter_path", str, "", "REA native adapter under verification") + + def running(self): + root = Path(ctx.options.rea_fixture_root) + spec = importlib.util.spec_from_file_location("rea_capture_under_test", ctx.options.rea_adapter_path) + adapter = importlib.util.module_from_spec(spec) + spec.loader.exec_module(adapter) + classifications = [ + (adapter.CaptureFailure("format", "Malformed retained record.", "/record"), "format"), + (adapter.CaptureFailure("input-limit", "Nesting budget.", "/extension"), "input-limit"), + (MemoryError("payload must not enter diagnostics"), "resource-limit"), + (RuntimeError("payload must not enter diagnostics"), "decoder"), + (OSError("payload must not enter diagnostics"), "decoder"), + ] + for error, expected in classifications: + reply = adapter.failure_reply(error) + assert reply["reason"] == expected, reply + assert "payload must not enter diagnostics" not in json.dumps(reply) + for state, expected in [({"metadata": {1: b"bad-key"}}, "format"), ({"metadata": object()}, "decoder")]: + try: + adapter.project_record(state, []) + except adapter.CaptureFailure as error: + assert error.reason == expected, (error.reason, expected) + assert error.pointer == "/metadata", error.pointer + else: + raise AssertionError("Invalid native representation was accepted") + non_utf8 = adapter.project_record({"request": {"headers": [(b"Referer", b"https://byte-user:byte-secret@example.test/path\xff")] }}, []) + assert non_utf8["reported"]["request"]["headers"][0][1] is None, non_utf8 + assert non_utf8["binary_fields"][-1]["state"] == "redacted", non_utf8 + for value in [" \tHTTPS://ows-user:ows-secret@example.test/path\t ", b" \tHTTPS://ows-user:ows-secret@example.test/path\t "]: + projected = adapter.project_record({"request": {"headers": [(b"Referer", value)]}}, []) + assert projected["reported"]["request"]["headers"][0][1] == " \tHTTPS://example.test/path\t ", projected + assert "ows-secret" not in json.dumps(projected), projected + if isinstance(value, bytes): + assert projected["binary_fields"][-1]["state"] == "redacted", projected + (root / "native-error-classifications.json").write_text(json.dumps({"passed": len(classifications) + 2})) + first = http.HTTPFlow(connection.Client(peername=("127.0.0.1", 1), sockname=("127.0.0.1", 2)), connection.Server(address=("example.test", 80))) + first.id = "original-producer-id" + first.request = http.Request.make("POST", "http://example.test/a?token=ordinary", b"\x00\xffbody", [(b"Authorization", b"Bearer native-transport-secret"), (b"X-Duplicate", b"one"), (b"X-Duplicate", b"two")]) + first.request.path = "http://user:password@example.test/a?token=ordinary#fragment" + first.response = http.Response.make(200, b"\x00\xfeanswer", [(b"Set-Cookie", b"session=cookie-secret"), (b"Location", b"https://redirect-user:redirect-password@example.test/b?token=ordinary")]) + first.metadata = {"big_integer": 9007199254740993, "nonfinite": float("inf"), "isLosslessNumber": True, "value": "ordinary-extension", "response": {"headers": [["Authorization", "ordinary-extension-credential-name"]]}} + first.websocket = WebSocketData(messages=[WebSocketMessage(2, True, b"\x00\xfdmessage", 123.5)]) + first.backup() + first.comment = "caller-selected-fixture" + second = first.copy() + second.id = first.id + second.request.raw_content = None + second.response.raw_content = b"" + second.websocket = None + with (root / "flows.mitm").open("wb") as handle: + writer = FlowWriter(handle) + writer.add(first) + writer.add(second) + har_first, har_second = first.copy(), second.copy() + har_first.request.path = "/a?token=ordinary#fragment" + har_second.request.path = "/a?token=ordinary#fragment" + har_first.response.raw_content = b"\x00\xff\x01\xfe" + markers = {"sensitive": "REDACTED", "bracket": "literal[", "overlap": "secret", "ordinary": "unmarked"} + har = SaveHar().make_har([har_first, har_second]) + har["log"]["entries"][0]["_markers"] = markers + har["log"]["entries"][0]["_private_properties"] = {"private-property/~": {"number": 123, "text": "private-property"}, "kept": 7} + (root / "producer.har").write_text(json.dumps(har)) + har_text = json.dumps(har) + for name, members in [ + ("equal-number", '"_duplicate": 1, "_duplicate": 1'), + ("equal-null", '"_duplicate": null, "_duplicate": null'), + ("equal-object", '"_duplicate": {"value": 1}, "_duplicate": {"value": 1}'), + ("escaped-key", '"_duplicate": 1, "\\u005fduplicate": 1'), + ]: + duplicate = har_text.replace('"cache": {}', '"cache": {}, ' + members, 1) + assert duplicate != har_text + (root / ("duplicate-" + name + ".har")).write_text(duplicate) + duplicate = har_text.replace('"version": "1.2"', '"version": "1.2", "version": "1.2"', 1) + assert duplicate != har_text + (root / "duplicate-core.har").write_text(duplicate) + prototype = json.loads(har_text) + prototype["_extension"] = {"__proto__": {"preserved": 7}, "constructor": "ordinary"} + (root / "prototype.har").write_text(json.dumps(prototype)) + prototype_native = first.get_state() + prototype_native["metadata"]["__proto__"] = {"preserved": 7} + with (root / "prototype.mitm").open("wb") as handle: + tnetstring.dump(prototype_native, handle) + invalid_har = json.loads(json.dumps(har)) + invalid_har["log"]["entries"][0]["response"]["content"].update({"encoding": "base64", "text": "AR=="}) + (root / "invalid-private-parent.har").write_text(json.dumps(invalid_har)) + strings = first.get_state() + for state in [strings, strings["backup"]]: + state["request"]["path"] = "https://native-user:string-password@example.test/string-path" + state["request"]["authority"] = "authority-user:authority-password@example.test" + state["request"]["headers"] = [(b"Referer", "https://referer-user:referer-password@example.test/from"), (b"Origin", "//origin-user:origin-password@example.test")] + state["response"]["headers"] = [(b"Location", "https://location-user:location-password@example.test/to")] + state["metadata"]["_markers"] = dict(markers) + state["metadata"]["_private_properties"] = {"private-property/~": {"number": 123, "bytes": b"private-property"}, "kept": 7} + with (root / "string-urls.mitm").open("wb") as handle: + tnetstring.dump(strings, handle) + strings["metadata"]["_private_properties"]["private-property/~"] = {1: b"unsupported-key"} + with (root / "invalid-private-key.mitm").open("wb") as handle: + tnetstring.dump(strings, handle) + extension = "leaf" + for _ in range(66): + extension = {"child": extension} + har["_extension"] = extension + (root / "deep.har").write_text(json.dumps(har)) + deep = first.get_state() + deep["_extension"] = extension + with (root / "deep.mitm").open("wb") as handle: + tnetstring.dump(deep, handle) + for name, value in [("invalid-credential-key", {1: b"bad-key"}), ("deep-credential", extension)]: + malformed = first.get_state() + malformed["request"]["headers"] = [(b"Authorization", value)] + with (root / (name + ".mitm")).open("wb") as handle: + tnetstring.dump(malformed, handle) + for prefix, name in [(" \t", "ows"), ("\r\n \v\f\x00\x1f", "controls")]: + url = prefix + "HTTPS://ows-user:ows-secret@example.test/path?token=ordinary#fragment\t " + relative = prefix + "//ows-user:ows-secret@example.test/path?token=ordinary#fragment\t " + whitespace_har = json.loads((root / "producer.har").read_text()) + entry = whitespace_har["log"]["entries"][0] + for side, header in [("response", "Location"), ("request", "Referer"), ("request", "Origin")]: + entry[side]["headers"] = [{"name": header, "value": url}] + entry[side]["headers"] + (root / (name + ".har")).write_text(json.dumps(whitespace_har)) + whitespace_native = first.get_state() + for state in [whitespace_native, whitespace_native["backup"]]: + state["request"]["headers"] = [(b"Referer", url.encode("utf-8")), (b"Origin", relative)] + state["response"]["headers"] = [(b"Location", url)] + with (root / (name + ".mitm")).open("wb") as handle: + tnetstring.dump(whitespace_native, handle) + non_utf8_native = first.get_state() + for state in [non_utf8_native, non_utf8_native["backup"]]: + state["request"]["path"] = b"https://byte-user:byte-secret@example.test/path\xff" + state["request"]["authority"] = b"byte-user:byte-secret@example.test\xff" + state["request"]["headers"] = [(b"Referer", b" https://byte-user:byte-secret@example.test/from\xff"), (b"Origin", b"//byte-user:byte-secret@example.test\xff")] + state["response"]["headers"] = [(b"Location", b"https://byte-user:byte-secret@example.test/to\xff")] + with (root / "non-utf8-urls.mitm").open("wb") as handle: + tnetstring.dump(non_utf8_native, handle) + (root / "oracle.json").write_text(json.dumps({"records": 2, "request_base64": "AP9ib2R5", "response_base64": "AP5hbnN3ZXI=", "websocket_base64": "AP1tZXNzYWdl", "id": first.id})) + ctx.master.shutdown() + +addons = [Generator()] diff --git a/scripts/generate-mcp-tool-catalog.mjs b/scripts/generate-mcp-tool-catalog.mjs index 7d44af653..4803cf8d2 100644 --- a/scripts/generate-mcp-tool-catalog.mjs +++ b/scripts/generate-mcp-tool-catalog.mjs @@ -17,8 +17,9 @@ for (const argument of arguments_) throw new Error(`Unknown MCP tool catalog option: ${argument}`); const root = join(dirname(fileURLToPath(import.meta.url)), ".."); -// The checked-in catalog is a portable package artifact. Keep its canonical -// provider metadata independent of the machine running the generator. +// The build-generated catalog is a portable package artifact that is not +// committed to git. Keep its canonical provider metadata independent of the +// machine running the generator. It is regenerated by `npm run build`. const CATALOG_PLATFORM = "darwin"; const sessionToolNames = new Set([ ...TOOL_CONTRACTS.filter(({ kind }) => kind === "session").map( diff --git a/scripts/hopper-demo-x11.py b/scripts/hopper-demo-x11.py index 936f8e9e7..470c9fd28 100644 --- a/scripts/hopper-demo-x11.py +++ b/scripts/hopper-demo-x11.py @@ -24,8 +24,10 @@ SUPPORTED_HOPPER_SHA256 = { "1339f9e58377442b0c6fcb0dfc3cec20d593cc557408521fad9a00dbc6b8da13", } EXPECTED_SCREEN = (1280, 1024) -EXPECTED_DIALOG = (189, 370, 901, 284) -DEMO_CLICK = (305, 632) +DIALOG_TITLES = {"Registration", "Registration — Hopper Disassembler"} +HOPPER_WINDOW_TITLE = "Hopper Disassembler" +DEMO_CLICK_LEFT = 116 +DEMO_CLICK_BOTTOM = 22 PRIVATE_DISPLAY_UNAVAILABLE = 70 X11_AUTHORIZATION_FAILED = 71 UNSUPPORTED_HOPPER_BUILD = 72 @@ -197,6 +199,47 @@ def windows(x11: ctypes.CDLL, display: int, root: int) -> list[tuple[int, int, i return found +def window_title(x11: ctypes.CDLL, display: int, window: int) -> str | None: + title = ctypes.c_char_p() + if not x11.XFetchName(display, window, ctypes.byref(title)) or not title.value: + return None + try: + return title.value.decode("utf-8", "replace") + finally: + x11.XFree(ctypes.cast(title, ctypes.c_void_p)) + + +def window_transient_for( + x11: ctypes.CDLL, display: int, window: int +) -> int | None: + parent = ctypes.c_ulong() + if not x11.XGetTransientForHint(display, window, ctypes.byref(parent)): + return None + return int(parent.value) + + +def find_demo_dialog( + x11: ctypes.CDLL, + display: int, + observed: list[tuple[int, int, int, int, int]], +) -> tuple[int, int, int, int, int] | None: + for candidate in observed: + window = candidate[0] + if window_title(x11, display, window) not in DIALOG_TITLES: + continue + parent = window_transient_for(x11, display, window) + if parent is None: + continue + if window_title(x11, display, parent) == HOPPER_WINDOW_TITLE: + return candidate + return None + + +def demo_click(dialog: tuple[int, int, int, int, int]) -> tuple[int, int]: + _window, x, y, _width, height = dialog + return x + DEMO_CLICK_LEFT, y + height - DEMO_CLICK_BOTTOM + + def configure_x11(x11: ctypes.CDLL, xtst: ctypes.CDLL) -> None: window_pointer = ctypes.POINTER(ctypes.c_ulong) x11.XOpenDisplay.argtypes = [ctypes.c_char_p] @@ -213,6 +256,18 @@ def configure_x11(x11: ctypes.CDLL, xtst: ctypes.CDLL) -> None: ctypes.POINTER(XWindowAttributes), ] x11.XGetWindowAttributes.restype = ctypes.c_int + x11.XFetchName.argtypes = [ + ctypes.c_void_p, + ctypes.c_ulong, + ctypes.POINTER(ctypes.c_char_p), + ] + x11.XFetchName.restype = ctypes.c_int + x11.XGetTransientForHint.argtypes = [ + ctypes.c_void_p, + ctypes.c_ulong, + ctypes.POINTER(ctypes.c_ulong), + ] + x11.XGetTransientForHint.restype = ctypes.c_int x11.XQueryTree.argtypes = [ ctypes.c_void_p, ctypes.c_ulong, @@ -645,6 +700,7 @@ def run_launch(args: argparse.Namespace) -> int: deadline = time.monotonic() + 10 observed: list[tuple[int, int, int, int, int]] = [] + dialog: tuple[int, int, int, int, int] | None = None while time.monotonic() < deadline: if socket.exists(): x11.XCloseDisplay(display) @@ -660,7 +716,8 @@ def run_launch(args: argparse.Namespace) -> int: "owned Hopper process exited", ) observed = windows(x11, display, root) - if any(window[1:] == EXPECTED_DIALOG for window in observed): + dialog = find_demo_dialog(x11, display, observed) + if dialog is not None: break time.sleep(0.1) else: @@ -676,8 +733,10 @@ def run_launch(args: argparse.Namespace) -> int: "expected Hopper demo dialog not found", ) + assert dialog is not None + click_x, click_y = demo_click(dialog) input_results = ( - xtst.XTestFakeMotionEvent(display, -1, DEMO_CLICK[0], DEMO_CLICK[1], 0), + xtst.XTestFakeMotionEvent(display, -1, click_x, click_y, 0), xtst.XTestFakeButtonEvent(display, 1, True, 0), xtst.XTestFakeButtonEvent(display, 1, False, 0), ) @@ -696,9 +755,8 @@ def run_launch(args: argparse.Namespace) -> int: finally: if display is not None: x11.XCloseDisplay(display) - if child is not None and child.poll() is None: - child.terminate() - child.wait() + if child is not None: + stop_process(child) stop_process(xvfb) capture.close() diff --git a/scripts/lib/catalog-builders.mjs b/scripts/lib/catalog-builders.mjs index 1e5ddfab6..3664e28aa 100644 --- a/scripts/lib/catalog-builders.mjs +++ b/scripts/lib/catalog-builders.mjs @@ -48,6 +48,8 @@ export const toolFamilyCatalog = (sources) => { ...sources.browserContracts.BROWSER_TOOL_CONTRACTS, ...sources.webRuntimeContracts.WEB_RUNTIME_TOOL_CONTRACTS, ...sources.browserScenarioContracts.BROWSER_SCENARIO_TOOL_CONTRACTS, + ...sources.webNetworkCaptureContracts + .WEB_NETWORK_CAPTURE_TOOL_CONTRACTS, ], }, { @@ -170,6 +172,17 @@ export const providerCatalog = (sources) => { identity: sources.androidProvider.JADX_PROVIDER_IDENTITY, contracts: sources.androidContracts.ANDROID_TOOL_CONTRACTS, }, + { + identity: sources.webNetworkCaptureProvider.HAR_CAPTURE_PROVIDER_IDENTITY, + contracts: + sources.webNetworkCaptureContracts.WEB_NETWORK_CAPTURE_TOOL_CONTRACTS, + }, + { + identity: + sources.webNetworkCaptureProvider.MITMPROXY_CAPTURE_PROVIDER_IDENTITY, + contracts: + sources.webNetworkCaptureContracts.WEB_NETWORK_CAPTURE_TOOL_CONTRACTS, + }, { identity: sources.browserProvider.CDP_BROWSER_PROVIDER_IDENTITY, contracts: [ diff --git a/scripts/lib/catalog-core.mjs b/scripts/lib/catalog-core.mjs index 311fb5f0a..c2b2018f8 100644 --- a/scripts/lib/catalog-core.mjs +++ b/scripts/lib/catalog-core.mjs @@ -51,6 +51,9 @@ const SOURCE_PATHS = { "dist/contracts/managed/managedWorkflowToolContracts.js", browserContracts: "dist/contracts/browserToolContracts.js", webRuntimeContracts: "dist/contracts/webRuntimeToolContracts.js", + webNetworkCaptureContracts: + "dist/contracts/webNetworkCaptureToolContracts.js", + webNetworkCaptureProvider: "dist/browser/history/CaptureRelease.js", browserScenarioContracts: "dist/contracts/browserScenarioToolContracts.js", electronContracts: "dist/contracts/javascript/electronToolContracts.js", javascriptRuntimeObservationContracts: @@ -75,7 +78,7 @@ const SOURCE_PATHS = { evidenceBundle: "dist/domain/evidenceBundle.js", evidenceCompletion: "dist/domain/evidenceCompletionLedger.js", completionGeneration: "dist/domain/completionLedgerGeneration.js", - processCapture: "dist/domain/processCapture.js", + processCapture: "dist/domain/process/processCapture.js", analysisSnapshot: "dist/domain/analysisSnapshot.js", artifactGraph: "dist/domain/artifactGraph.js", browserObservation: "dist/domain/browserObservation.js", diff --git a/scripts/lib/docs-facts.mjs b/scripts/lib/docs-facts.mjs index 32e6fbb54..ad50dbc8e 100644 --- a/scripts/lib/docs-facts.mjs +++ b/scripts/lib/docs-facts.mjs @@ -75,6 +75,11 @@ export const documentationFactIssues = async (root, catalog) => { for (const path of README_PATHS) { const content = await readFile(join(root, path), "utf8"); requireText(issues, path, content, "MCP-tool_catalog"); + if (path === "README.md") { + requireText(issues, path, content, "(docs/installation.md"); + requireText(issues, path, content, "(docs/product-catalog.json)"); + continue; + } for (const client of catalog.setup_clients) requireText(issues, path, content, client.display_name); try { @@ -88,6 +93,11 @@ export const documentationFactIssues = async (root, catalog) => { } } + const installationPath = "docs/installation.md"; + const installation = await readFile(join(root, installationPath), "utf8"); + for (const client of catalog.setup_clients) + requireText(issues, installationPath, installation, client.display_name); + const agents = await readFile(join(root, "AGENTS.md"), "utf8"); requireText(issues, "AGENTS.md", agents, "docs/product-catalog.json"); @@ -95,8 +105,6 @@ export const documentationFactIssues = async (root, catalog) => { const template = await readFile(join(root, templatePath), "utf8"); requireText(issues, templatePath, template, "docs/product-catalog.json"); - const english = await readFile(join(root, "README.md"), "utf8"); - requireText(issues, "README.md", english, "docs/product-catalog.json"); return issues; }; diff --git a/scripts/lib/generated-file.mjs b/scripts/lib/generated-file.mjs index d7df714c6..e296210d0 100644 --- a/scripts/lib/generated-file.mjs +++ b/scripts/lib/generated-file.mjs @@ -33,7 +33,7 @@ export const ensureGeneratedFile = async ({ return { changed: false }; if (check) throw new Error( - `${path} is missing or stale. Run \`${generateCommand}\` and commit the result.`, + `${path} is missing or stale. Run \`${generateCommand}\` to regenerate it; commit the result if the file is tracked.`, ); await writeFile(path, preserveLineEndings(source, existing), "utf8"); return { changed: true }; diff --git a/scripts/lib/module-boundaries.d.mts b/scripts/lib/module-boundaries.d.mts index 51d5957c6..e8b6d2dd6 100644 --- a/scripts/lib/module-boundaries.d.mts +++ b/scripts/lib/module-boundaries.d.mts @@ -6,6 +6,7 @@ export interface ModuleBoundaryViolation { readonly target: string; readonly boundary: | "artifact-acquisition" + | "process-capture" | "pure-layer" | "application-composition" | "provider-construction" diff --git a/scripts/lib/module-boundaries.mjs b/scripts/lib/module-boundaries.mjs index a0c71738b..b50050d79 100644 --- a/scripts/lib/module-boundaries.mjs +++ b/scripts/lib/module-boundaries.mjs @@ -60,6 +60,14 @@ const failedBoundary = (file, target) => { if (!file.startsWith("src/") || !target.startsWith("src/")) return undefined; const layer = file.split("/")[1]; const targetLayer = target.split("/")[1]; + if ( + file.startsWith("src/process/capture/") && + (["application", "composition", "server", "cli", "main"].includes( + targetLayer, + ) || + /^src\/(?:cli|main)\./u.test(target)) + ) + return "process-capture"; if ( file.startsWith("src/application/javascript/") && /^src\/artifacts\/(?:Asar|Directory)ArtifactReader\.(?:js|ts)$/u.test( @@ -70,6 +78,8 @@ const failedBoundary = (file, target) => { if ( (file.startsWith("src/artifacts/javascript/") || file.startsWith("src/artifacts/apple/") || + file.startsWith("src/artifacts/inventory/") || + file.startsWith("src/artifacts/extraction/") || /^src\/artifacts\/ArtifactHash\.(?:js|ts)$/u.test(file)) && (["application", "composition", "server", "cli", "main"].includes( targetLayer, diff --git a/scripts/rea.mjs b/scripts/rea.mjs index 4e310bf08..1f720a4d1 100755 --- a/scripts/rea.mjs +++ b/scripts/rea.mjs @@ -19,7 +19,7 @@ const runtimeFiles = isMcpMode ? ["dist/main.js"] : isMcpDoctorMode ? ["dist/main.js", "dist/mcpDoctor.js"] - : ["dist/cli.js", "dist/cliOutput.js"]; + : ["dist/cli.js", "dist/cliOutput.js", "dist/cli/streamedJsonOutput.js"]; if (!(await compiledRuntimeExists(runtimeFiles))) { process.stderr.write( @@ -39,6 +39,8 @@ if (!(await compiledRuntimeExists(runtimeFiles))) { process.exitCode = result.exitCode; } else { const { createCli } = await import("../dist/cli.js"); + const { createStreamedCliJsonOutput } = + await import("../dist/cli/streamedJsonOutput.js"); const { renderCliOutputArgumentError, renderEmptyFilteredCliOutput, @@ -51,14 +53,20 @@ if (!(await compiledRuntimeExists(runtimeFiles))) { process.exitCode = 1; } else { let wroteOutput = false; - await createCli().serve(args, { + const resultOutput = createStreamedCliJsonOutput(args, process.stdout); + await createCli(process.env, resultOutput).serve(args, { stdout: (output) => { + if (resultOutput?.handled) { + if (resultOutput.failed) + process.stderr.write(sanitizeCliOutput(output)); + return; + } const sanitized = sanitizeCliOutput(output); if (sanitized.length > 0) wroteOutput = true; process.stdout.write(sanitized); }, }); - if (!wroteOutput) + if (!wroteOutput && !resultOutput?.handled) process.stdout.write(renderEmptyFilteredCliOutput(args) ?? ""); } } diff --git a/scripts/verify-native-ui.mjs b/scripts/verify-native-ui.mjs index f76af57d1..34616a72a 100644 --- a/scripts/verify-native-ui.mjs +++ b/scripts/verify-native-ui.mjs @@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js"; import { observeNativeUi } from "../dist/native/NativeUiObservation.js"; +import { NATIVE_UI_HELPER_MAX_BUFFER } from "../dist/native/NativeUiOutputBudget.js"; if (process.platform !== "darwin") throw new Error( @@ -30,7 +31,38 @@ try { "-o", childRetrievalTest, ]); - await promisify(execFile)(childRetrievalTest, []); + const childRetrieval = await promisify(execFile)(childRetrievalTest, []); + const [slashRichProducerJson, numericProducerJson] = childRetrieval.stdout + .trimEnd() + .split("\n"); + if ( + slashRichProducerJson === undefined || + numericProducerJson === undefined + ) { + throw new Error( + "Native UI serializer seam did not emit its producer bytes", + ); + } + const slashRichConsumerJson = JSON.stringify( + JSON.parse(slashRichProducerJson), + ); + if (slashRichProducerJson !== slashRichConsumerJson) { + throw new Error( + "Swift helper JSON bytes differ from the normalized JavaScript consumer representation", + ); + } + const numericConsumerJson = JSON.stringify(JSON.parse(numericProducerJson)); + if ( + Buffer.byteLength(numericProducerJson) <= + Buffer.byteLength(numericConsumerJson) + ) { + throw new Error( + "Foundation numeric fixture no longer exercises larger producer JSON bytes", + ); + } + if (Buffer.byteLength(numericProducerJson) > NATIVE_UI_HELPER_MAX_BUFFER) { + throw new Error("Foundation numeric fixture exceeds the raw helper budget"); + } const contents = join(root, "Fixture.app", "Contents"); await mkdir(join(contents, "MacOS"), { recursive: true }); diff --git a/scripts/verify-package-install.mjs b/scripts/verify-package-install.mjs index da2e79f24..81ee5afc6 100644 --- a/scripts/verify-package-install.mjs +++ b/scripts/verify-package-install.mjs @@ -31,7 +31,7 @@ export async function verifyPackageInstall({ const processCaptureCapabilityUrl = pathToFileURL( join( prefix, - "lib/node_modules/rea-agents/dist/application/ProcessCaptureCapability.js", + "lib/node_modules/rea-agents/dist/process/capture/ProcessCaptureCapability.js", ), ).href; const processCaptureCapability = json( @@ -77,7 +77,7 @@ export async function verifyPackageInstall({ const noOptionalCapabilityUrl = pathToFileURL( join( noOptionalPrefix, - "lib/node_modules/rea-agents/dist/application/ProcessCaptureCapability.js", + "lib/node_modules/rea-agents/dist/process/capture/ProcessCaptureCapability.js", ), ).href; const noOptionalCapability = json( diff --git a/scripts/verify-package-mcp.mjs b/scripts/verify-package-mcp.mjs index a4831d825..3b5be1b5d 100644 --- a/scripts/verify-package-mcp.mjs +++ b/scripts/verify-package-mcp.mjs @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import { join } from "node:path"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; +import { readFile, rm } from "node:fs/promises"; import { Client } from "@modelcontextprotocol/client"; import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; @@ -167,6 +168,7 @@ const verifyMcpInlineArtifactEvidence = async ( mcpOptions, ); assert.notEqual(opened.isError, true, JSON.stringify(opened)); + let outputRoot; try { const inspected = await client.callTool( { name: "inspect_artifact", arguments: {} }, @@ -212,12 +214,35 @@ const verifyMcpInlineArtifactEvidence = async ( evidence, ); } - } finally { - const closed = await client.callTool( - { name: "close_binary", arguments: {} }, + const extracted = await client.callTool( + { name: "extract_artifact", arguments: {} }, mcpOptions, ); - assert.notEqual(closed.isError, true, JSON.stringify(closed)); + assert.notEqual(extracted.isError, true, JSON.stringify(extracted)); + const extraction = parseEvidence(extracted.structuredContent?.evidence); + assert.deepEqual( + extraction.normalized_result, + extracted.structuredContent?.result, + ); + const materialized = extracted.structuredContent?.result; + assert.equal(typeof materialized?.output_root, "string"); + outputRoot = materialized.output_root; + assert.equal(materialized.containment_verified, true); + assert.equal( + await readFile(join(outputRoot, "app/main.js"), "utf8"), + "main();", + ); + } finally { + try { + const closed = await client.callTool( + { name: "close_binary", arguments: {} }, + mcpOptions, + ); + assert.notEqual(closed.isError, true, JSON.stringify(closed)); + } finally { + if (outputRoot !== undefined) + await rm(outputRoot, { recursive: true, force: true }); + } } }; diff --git a/scripts/verify-packaged-ghidra-windows.mjs b/scripts/verify-packaged-ghidra-windows.mjs index 1dfe3cd34..fa17c856c 100644 --- a/scripts/verify-packaged-ghidra-windows.mjs +++ b/scripts/verify-packaged-ghidra-windows.mjs @@ -25,10 +25,12 @@ if (process.platform !== "win32" || process.arch !== "x64") ); const workspace = await mkdtemp(join(tmpdir(), "rea-packaged-ghidra-")); const exec = promisify(execFile); -let packageRoot = resolve(process.argv[2] ?? "."); +const x86Fixture = process.argv[2] === "--x86"; +const packageRootArgument = x86Fixture ? undefined : process.argv[2]; +let packageRoot = resolve(packageRootArgument ?? "."); // The default lane verifies the npm artifact in an isolated prefix. Explicit // package roots support an already-installed artifact without a second install. -if (process.argv[2] === undefined) { +if (packageRootArgument === undefined) { try { const npmEntry = process.env.npm_execpath ?? @@ -77,7 +79,10 @@ if (process.argv[2] === undefined) { } } const target = resolve( - process.argv[3] ?? "build/fixtures/rea-ghidra-windows.exe", + process.argv[3] ?? + (x86Fixture + ? "build/fixtures/rea-ghidra-windows-x86.exe" + : "build/fixtures/rea-ghidra-windows.exe"), ); let TOOL_CONTRACTS, native, token, sha256; try { @@ -215,7 +220,8 @@ try { }; try { await client.connect(transport); - await call("open_binary", { path: target, provider_id: "ghidra" }); + await call("open_binary", { path: target }); + await call("binary_overview", {}); assert.equal((await call("list_documents", {})).length, 1); await call("list_names", {}); const procedures = await call("list_procedures", {}); diff --git a/scripts/verify-real-ghidra-windows.mjs b/scripts/verify-real-ghidra-windows.mjs index 8dd9f6b53..f245a4a9f 100644 --- a/scripts/verify-real-ghidra-windows.mjs +++ b/scripts/verify-real-ghidra-windows.mjs @@ -63,12 +63,20 @@ if ( ); const root = resolve(fileURLToPath(new URL("..", import.meta.url))); -const targetPath = resolve(root, "build", "fixtures", "rea-ghidra-windows.exe"); +const architecture = process.argv[2] === "--x86" ? "x86" : "x86_64"; +const targetPath = resolve( + root, + "build", + "fixtures", + architecture === "x86" + ? "rea-ghidra-windows-x86.exe" + : "rea-ghidra-windows.exe", +); const target = await parseBinaryTarget(targetPath); if ( !target.ok || target.value.format !== "pe" || - target.value.architecture !== "x86_64" || + target.value.architecture !== architecture || target.value.executableRole !== "application" || target.value.managed !== false ) @@ -113,6 +121,9 @@ try { target.value.sha256, profile.value.profile.digest, ); + const languagePrefix = architecture === "x86" ? "x86:LE:32:" : "x86:LE:64:"; + if (!started.value.target.language_id.startsWith(languagePrefix)) + throw new Error("Windows fixture imported with the wrong Ghidra language"); coordinates = client.diagnostics(); assertRuntimeCoordinates(coordinates, target.value.sha256); @@ -137,6 +148,19 @@ try { ); if (procedure === undefined) throw new Error("Windows Ghidra fixture exposed no local procedure"); + const calleeAddress = architecture === "x86" ? 0x401010n : 0x140001010n; + const callee = procedures.find( + (item) => BigInt(item.address) === calleeAddress, + ); + if (callee === undefined) + throw new Error("Windows fixture exposed no controlled callee"); + const pseudoCode = await functionOperation("procedure_pseudo_code", { + procedure: callee.value, + }); + if (typeof pseudoCode !== "string" || !/\b(?:42|0x2a)\b/iu.test(pseudoCode)) + throw new Error( + "Windows fixture decompilation lost its known return value", + ); await inventory("inspect_native_load_image", {}); const memory = await inventory("read_bytes", { @@ -210,6 +234,7 @@ try { report = { ok: true, provider: { id: "ghidra", version: SUPPORTED_GHIDRA_VERSION }, + language_id: started.value.target.language_id, target: { format: target.value.format, architecture: target.value.architecture, @@ -228,7 +253,7 @@ try { limitations: [ "approved-non-sensitive-fixtures-only", "windows-x64-local-ntfs-only", - "native-x86-64-pe-applications-only", + "native-x86-and-x86-64-pe-applications-only", "no-gui-or-mutation-authority", ], }; diff --git a/scripts/verify-web-network-captures.mjs b/scripts/verify-web-network-captures.mjs new file mode 100644 index 000000000..d5d1e2609 --- /dev/null +++ b/scripts/verify-web-network-captures.mjs @@ -0,0 +1,994 @@ +#!/usr/bin/env node +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { createHash, randomUUID } from "node:crypto"; +import { + access, + copyFile, + chmod, + readFile, + truncate, + writeFile, +} from "node:fs/promises"; +import { dirname, isAbsolute, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { Client } from "@modelcontextprotocol/client"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js"; +import { runOwnedCommand } from "../dist/process/OwnedCommand.js"; +import { WEB_NETWORK_CAPTURE_LIMITS } from "../dist/domain/webNetworkCapture.js"; +import { mcpTextValue } from "./lib/mcp-verifier-results.mjs"; +import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs"; + +const executable = process.env.REA_MITMDUMP_COMMAND; +if ( + process.platform !== "linux" || + executable === undefined || + !isAbsolute(executable) +) + throw new Error( + "verify:web:network-captures requires Linux and absolute REA_MITMDUMP_COMMAND for caller-supplied mitmdump 12.2.3", + ); +await access(executable); +const entrypoint = + process.argv[2] ?? fileURLToPath(new URL("./rea.mjs", import.meta.url)); +const run = createVerifierRun(); +const runtime = await PrivateRuntimeRoot.create({ + prefix: "rea-history-verifier-", +}); +const environment = Object.fromEntries( + Object.entries(process.env).filter(([, value]) => typeof value === "string"), +); +const client = new Client({ + name: "historical-web-capture-verifier", + version: "1", +}); +const transport = new StdioClientTransport({ + command: process.execPath, + args: [entrypoint, "mcp"], + env: environment, + stderr: "pipe", +}); +const failures = []; +let cases = 0; +try { + await runOwnedCommand( + { + command: executable, + arguments: [ + "--no-server", + "-q", + "--set", + `confdir=${join(runtime.path, "config")}`, + "--set", + `rea_fixture_root=${runtime.path}`, + "--set", + `rea_adapter_path=${resolve(dirname(entrypoint), "../bridge/mitmproxy/capture.py")}`, + "-s", + fileURLToPath( + new URL("./fixtures/generate-mitmproxy-capture.py", import.meta.url), + ), + ], + cwd: runtime.path, + runId: `rea-history-fixture-${randomUUID()}`, + hostEnvironment: environment, + }, + { timeoutMs: 30_000, diagnosticBytes: 1024 * 1024 }, + ); + assert.deepEqual( + JSON.parse( + await readFile( + join(runtime.path, "native-error-classifications.json"), + "utf8", + ), + ), + { passed: 7 }, + ); + await client.connect(transport); + for (const format of ["har", "mitmproxy"]) { + const path = join( + runtime.path, + format === "har" ? "producer.har" : "flows.mitm", + ); + const raw = await readFile(path); + const input = { capture_path: path, format, record_ordinals: [1, 0] }; + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, input); + assert.equal(value.total_records, 2); + assert.deepEqual( + value.records.map((record) => record.ordinal), + [1, 0], + ); + assert.equal( + value.artifact.sha256, + createHash("sha256").update(raw).digest("hex"), + ); + assert.equal(value.runtime_attribution, "unknown"); + const first = value.records[1]; + if (format === "mitmproxy") { + assert.equal(first.reported.id, "original-producer-id"); + assert.equal(value.records[0].reported.id, first.reported.id); + assert.equal(first.location.offset, 0); + assert.equal( + value.records[0].location.offset + value.records[0].location.bytes, + raw.length, + ); + assert.equal( + first.reported.request.path, + "http://example.test/a?token=ordinary#fragment", + ); + assertBinary( + first, + "/request/content", + Buffer.from([0, 255, ...Buffer.from("body")]), + ); + assertBinary( + first, + "/response/content", + Buffer.from([0, 254, ...Buffer.from("answer")]), + ); + assertBinary( + first, + "/websocket/messages/0/2", + Buffer.from([0, 253, ...Buffer.from("message")]), + ); + assert.equal(value.records[0].reported.request.content, null); + assertBinary(value.records[0], "/response/content", Buffer.alloc(0)); + assert.ok( + first.numeric_literals.some( + (number) => + number.pointer === "/metadata/big_integer" && + number.literal === "9007199254740993", + ), + ); + assert.ok( + first.numeric_literals.some( + (number) => + number.pointer === "/metadata/nonfinite" && + number.literal === "inf", + ), + ); + assert.equal( + first.reported.metadata.response.headers[0][1], + "ordinary-extension-credential-name", + ); + } else { + assert.equal(value.container.reported.log.creator.name, "mitmproxy"); + assert.equal(value.container.reported.log.creator.version, "12.2.3"); + assert.equal(value.records[0].reported.request.postData.text, null); + assertBinary( + first, + "/response/content/text", + Buffer.from([0, 255, 1, 254]), + ); + } + const text = JSON.stringify(value); + for (const secret of [ + "native-transport-secret", + "cookie-secret", + "redirect-password", + "user:password", + ]) + assert.ok( + !text.includes(secret), + `Credential leaked through ${mode}/${format}`, + ); + cases++; + } + } + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, { + capture_path: join(runtime.path, "string-urls.mitm"), + format: "mitmproxy", + }); + const first = value.records[0]; + for (const state of [first.reported, first.reported.backup]) { + assert.equal(state.request.path, "https://example.test/string-path"); + assert.equal(state.request.authority, "example.test"); + assert.deepEqual( + state.request.headers.map((field) => field[1]), + ["https://example.test/from", "//example.test"], + ); + assert.equal(state.response.headers[0][1], "https://example.test/to"); + } + assert.ok(!JSON.stringify(value).includes("password")); + assert.ok( + first.redactions.some( + (redaction) => + redaction.pointer === "/backup/request/authority" && + redaction.reason === "transport-credential", + ), + ); + cases++; + } + for (const sensitive_values of [ + ["REDACTED", "["], + ["secret", "REDACTED"], + ]) { + for (const format of ["har", "mitmproxy"]) { + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, { + capture_path: join( + runtime.path, + format === "har" ? "producer.har" : "string-urls.mitm", + ), + format, + record_ordinals: [0], + sensitive_values, + }); + const first = value.records[0]; + const markers = + format === "har" + ? first.reported._markers + : first.reported.metadata._markers; + assert.equal(markers.sensitive, null); + assert.equal(markers.ordinary, "unmarked"); + assert.equal( + sensitive_values.includes("[") ? markers.bracket : markers.overlap, + null, + ); + if (format === "mitmproxy") + assert.equal(first.reported.backup.metadata._markers.sensitive, null); + cases++; + } + } + } + for (const name of [ + "equal-number", + "equal-null", + "equal-object", + "escaped-key", + "core", + ]) { + const path = join(runtime.path, `duplicate-${name}.har`); + const original = await readFile(path); + for (const mode of ["cli", "mcp"]) { + await inspect( + mode, + { capture_path: path, format: "har" }, + "invalid_input", + ); + assert.deepEqual(await readFile(path), original); + cases++; + } + } + for (const format of ["har", "mitmproxy"]) { + const path = join( + runtime.path, + format === "har" ? "prototype.har" : "prototype.mitm", + ); + const original = await readFile(path); + for (const mode of ["cli", "mcp"]) { + const error = await inspect( + mode, + { capture_path: path, format }, + "unsupported_provider", + ); + assert.ok( + JSON.stringify(error).includes("JSON schema boundary cannot preserve"), + ); + assert.deepEqual(await readFile(path), original); + cases++; + } + } + for (const format of ["har", "mitmproxy"]) { + const path = join(runtime.path, `malformed-${format}`); + await writeFile(path, format === "har" ? "{invalid-json" : "999999999999:"); + for (const mode of ["cli", "mcp"]) { + await inspect(mode, { capture_path: path, format }, "invalid_input"); + cases++; + } + } + for (const format of ["har", "mitmproxy"]) { + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, { + capture_path: join( + runtime.path, + format === "har" ? "producer.har" : "string-urls.mitm", + ), + format, + sensitive_values: ["private-property"], + }); + const record = value.records[0]; + const properties = + format === "har" + ? record.reported._private_properties + : record.reported.metadata._private_properties; + assert.deepEqual(properties, { kept: 7 }); + assert.ok(!JSON.stringify(value).includes("private-property")); + assert.ok( + record.redactions.some( + (item) => + item.scope === "property-name" && + item.pointer === + (format === "har" + ? "/_private_properties" + : "/metadata/_private_properties"), + ), + ); + if (format === "mitmproxy") + assert.deepEqual(record.reported.backup.metadata._private_properties, { + kept: 7, + }); + cases++; + } + } + for (const [fixture, prefix] of [ + ["ows", " \t"], + ["controls", "\r\n \v\f\x00\x1f"], + ]) { + for (const format of ["har", "mitmproxy"]) { + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, { + capture_path: join( + runtime.path, + `${fixture}.${format === "har" ? "har" : "mitm"}`, + ), + format, + }); + assert.ok(!JSON.stringify(value).includes("ows-secret")); + const record = value.records[0]; + const states = + format === "har" + ? [record.reported] + : [record.reported, record.reported.backup]; + for (const state of states) { + if (format === "har") { + for (const side of ["request", "response"]) + assert.ok( + state[side].headers.some( + (header) => + header.value === + `${prefix}HTTPS://example.test/path?token=ordinary#fragment\t `, + ), + ); + } else { + assert.deepEqual(state.request.headers, [ + [ + "Referer", + `${prefix}HTTPS://example.test/path?token=ordinary#fragment\t `, + ], + [ + "Origin", + `${prefix}//example.test/path?token=ordinary#fragment\t `, + ], + ]); + assert.deepEqual(state.response.headers, [ + [ + "Location", + `${prefix}HTTPS://example.test/path?token=ordinary#fragment\t `, + ], + ]); + } + } + if (format === "mitmproxy") { + for (const pointer of [ + "/request/headers/0/1", + "/backup/request/headers/0/1", + ]) + assert.deepEqual( + record.binary_fields.find((field) => field.pointer === pointer), + { + pointer, + representation: "producer-bytes", + state: "redacted", + content_base64: null, + bytes: null, + sha256: null, + }, + ); + } + cases++; + } + } + } + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, { + capture_path: join(runtime.path, "non-utf8-urls.mitm"), + format: "mitmproxy", + }); + const record = value.records[0]; + assert.ok(!JSON.stringify(value).includes("byte-secret")); + for (const prefix of ["", "/backup"]) { + for (const suffix of [ + "/request/path", + "/request/authority", + "/request/headers/0/1", + "/request/headers/1/1", + "/response/headers/0/1", + ]) { + const pointer = prefix + suffix; + assert.deepEqual( + record.binary_fields.find((field) => field.pointer === pointer), + { + pointer, + representation: "producer-bytes", + state: "redacted", + content_base64: null, + bytes: null, + sha256: null, + }, + ); + } + } + cases++; + } + const nativeCoordinateInput = { + capture_path: join(runtime.path, "flows.mitm"), + format: "mitmproxy", + }; + const nativeCoordinates = await inspect("mcp", nativeCoordinateInput); + const originalLocation = nativeCoordinates.records[1].location; + assert.equal(originalLocation.kind, "byte-range"); + for (const literal of [ + String(originalLocation.offset), + String(originalLocation.bytes), + ]) { + for (const mode of ["cli", "mcp"]) { + const excluded = await inspect(mode, { + ...nativeCoordinateInput, + sensitive_values: [literal], + }); + assert.deepEqual(excluded.records[1].location, { + kind: "unknown", + reason: "explicit-sensitive-value", + }); + cases++; + } + } + cases++; + const deniedPath = join(runtime.path, "unreadable.har"); + await copyFile(join(runtime.path, "producer.har"), deniedPath); + await chmod(deniedPath, 0o000); + for (const mode of ["cli", "mcp"]) { + for (const format of ["har", "mitmproxy"]) { + const failure = await inspect( + mode, + { capture_path: deniedPath, format }, + "unavailable", + ); + assert.equal(failure.code, "access_denied"); + assert.equal(failure.details.system_code, "EACCES"); + assert.equal(failure.details.path, deniedPath); + cases++; + } + for (const literal of ["log", "entries"]) { + const value = await inspect(mode, { + capture_path: join(runtime.path, "producer.har"), + format: "har", + sensitive_values: [literal], + }); + assert.equal(value.container.records_pointer, null); + assert.ok( + value.records.every( + (record) => + record.location.kind === "unknown" && + record.location.reason === "explicit-sensitive-value", + ), + ); + assert.ok(!JSON.stringify(value).includes(literal)); + cases++; + } + for (const format of ["har", "mitmproxy"]) { + const value = await inspect(mode, { + capture_path: join( + runtime.path, + format === "har" ? "producer.har" : "string-urls.mitm", + ), + format, + sensitive_values: ["~1"], + }); + assert.ok(!JSON.stringify(value).includes("~1")); + cases++; + } + } + for (const mode of ["cli", "mcp"]) { + for (const format of ["har", "mitmproxy"]) { + for (const sensitive_values of [ + ["producer"], + ["producer", "REDACTED", "…"], + ]) { + await inspect(mode, { + capture_path: join( + runtime.path, + format === "har" ? "producer.har" : "flows.mitm", + ), + format, + sensitive_values, + }); + cases++; + } + } + } + for (const mode of ["cli", "mcp"]) { + for (const selection of [ + { record_ordinals: [12345], sensitive_values: ["12345"] }, + { record_ordinals: [0, 0], sensitive_values: ["Each"] }, + ]) { + await inspect( + mode, + { + capture_path: join(runtime.path, "producer.har"), + format: "har", + ...selection, + }, + "invalid_input", + ); + cases++; + } + } + await inspect( + "mcp", + { + capture_path: "relative.har", + format: "har", + sensitive_values: ["absolute"], + }, + "invalid_input", + ); + cases++; + const privatePath = join(runtime.path, "REDACTED.har"); + for (const name of ["mysecret", "/ordinary/secret"]) { + const unknownArgument = await inspect( + "mcp", + { + capture_path: join(runtime.path, "producer.har"), + format: "har", + sensitive_values: ["secret"], + [name]: true, + }, + "invalid_input", + ); + assert.deepEqual(unknownArgument.details.issues[0].path, []); + cases++; + } + await inspect("mcp", { + capture_path: join(runtime.path, "flows.mitm"), + format: "mitmproxy", + sensitive_values: ["\ud800", "ordinary-scalar"], + }); + cases++; + const replacementHar = JSON.parse( + await readFile(join(runtime.path, "producer.har"), "utf8"), + ); + replacementHar.log.entries[0].response.content = { + size: 3, + mimeType: "application/octet-stream", + encoding: "base64", + text: "77+9", + }; + const replacementHarPath = join(runtime.path, "unicode-scalar.har"); + await writeFile(replacementHarPath, JSON.stringify(replacementHar)); + for (const literal of ["\ud800", "\udfff", "\ufffd"]) { + const unicode = await inspect("mcp", { + capture_path: replacementHarPath, + format: "har", + sensitive_values: [literal], + }); + const body = unicode.records[0].binary_fields.find( + (field) => field.pointer === "/response/content/text", + ); + assert.equal(body?.state, literal === "\ufffd" ? "redacted" : "retained"); + assert.equal(body.content_base64, literal === "\ufffd" ? null : "77+9"); + cases++; + } + const numericHar = ( + await readFile(join(runtime.path, "producer.har"), "utf8") + ).replace('"cache": {}', '"cache": {}, "_big": 9007199254740993'); + const numericPath = join(runtime.path, "numeric.har"); + await writeFile(numericPath, numericHar); + assert.ok(numericHar.includes('"_big": 9007199254740993')); + for (const mode of ["cli", "mcp"]) { + for (const format of ["har", "mitmproxy"]) { + for (const literal of ["9007199254740993", "4740993"]) { + const value = await inspect(mode, { + capture_path: + format === "har" ? numericPath : join(runtime.path, "flows.mitm"), + format, + sensitive_values: [literal], + }); + assert.ok(!JSON.stringify(value).includes(literal)); + const first = value.records[0]; + const pointer = format === "har" ? "/_big" : "/metadata/big_integer"; + assert.ok( + first.redactions.some( + (redaction) => + redaction.pointer === pointer && + redaction.reason === "explicit-sensitive-value", + ), + ); + assert.ok( + first.numeric_literals.every( + (number) => !number.literal.includes(literal), + ), + ); + cases++; + } + } + } + for (const mode of ["cli", "mcp"]) { + for (const format of ["har", "mitmproxy"]) { + await inspect(mode, { + capture_path: join( + runtime.path, + format === "har" ? "producer.har" : "flows.mitm", + ), + format, + sensitive_values: [format], + }); + cases++; + } + } + const encodedHar = JSON.parse( + await readFile(join(runtime.path, "producer.har"), "utf8"), + ); + encodedHar.log.entries[0].response.content = { + size: 6, + mimeType: "application/octet-stream", + encoding: "base64", + text: "c2 VjcmV0", + }; + const encodedHarPath = join(runtime.path, "encoded-sensitive.har"); + await writeFile(encodedHarPath, JSON.stringify(encodedHar)); + for (const mode of ["cli", "mcp"]) { + for (const format of ["har", "mitmproxy"]) { + const bytes = + format === "har" + ? Buffer.from("secret") + : Buffer.from([0, 255, ...Buffer.from("body")]); + for (const literal of [ + bytes.toString("base64"), + bytes.toString("base64").slice(0, 4), + createHash("sha256").update(bytes).digest("hex"), + ]) { + const value = await inspect(mode, { + capture_path: + format === "har" + ? encodedHarPath + : join(runtime.path, "flows.mitm"), + format, + sensitive_values: [literal], + }); + const pointer = + format === "har" ? "/response/content/text" : "/request/content"; + const field = value.records[0].binary_fields.find( + (field) => field.pointer === pointer, + ); + assert.equal(field?.state, "redacted"); + assert.equal(field.content_base64, null); + assert.equal(field.sha256, null); + assert.ok(!JSON.stringify(value).includes(literal)); + cases++; + } + } + } + for (const mode of ["cli", "mcp"]) { + for (const format of ["har", "mitmproxy"]) { + const capturePath = join( + runtime.path, + format === "har" ? "producer.har" : "flows.mitm", + ); + const digest = createHash("sha256") + .update(await readFile(capturePath)) + .digest("hex"); + for (const literal of [digest, digest.slice(0, 12)]) { + const value = await inspect(mode, { + capture_path: capturePath, + format, + sensitive_values: [literal], + }); + assert.equal(value.artifact.sha256, null); + assert.equal(value.artifact.path, capturePath); + assert.ok(!JSON.stringify(value).includes(literal)); + cases++; + } + } + } + const oversizedPath = join(runtime.path, "oversized-capture"); + for (const mode of ["cli", "mcp"]) { + for (const literal of ["log", "entries"]) { + const hidden = await inspect(mode, { + capture_path: join(runtime.path, "producer.har"), + format: "har", + sensitive_values: [literal], + }); + assert.equal(hidden.total_records, 2); + assert.equal(hidden.container.records_pointer, null); + for (const [ordinal, record] of hidden.records.entries()) { + assert.equal(record.ordinal, ordinal); + assert.deepEqual(record.location, { + kind: "unknown", + reason: "explicit-sensitive-value", + }); + assert.equal(record.reported, null); + assert.deepEqual(record.binary_fields, []); + assert.deepEqual(record.numeric_literals, []); + } + cases++; + } + } + await writeFile(oversizedPath, ""); + await truncate(oversizedPath, WEB_NETWORK_CAPTURE_LIMITS.inputBytes + 1); + await copyFile(join(runtime.path, "producer.har"), privatePath); + for (const mode of ["cli", "mcp"]) { + for (const format of ["har", "mitmproxy"]) { + const failure = await inspect( + mode, + { capture_path: oversizedPath, format }, + "invalid_input", + ); + assert.equal(failure.details.issues[0].reason, "out_of_range"); + assert.deepEqual(failure.details.issues[0].path, ["capture_path"]); + assert.equal( + failure.details.issues[0].expected.maximum_capture_bytes, + WEB_NETWORK_CAPTURE_LIMITS.inputBytes, + ); + cases++; + } + for (const format of ["har", "mitmproxy"]) { + const failure = await inspect( + mode, + { + capture_path: join( + runtime.path, + format === "har" ? "deep.har" : "deep.mitm", + ), + format, + }, + "invalid_input", + ); + const issue = failure.details.issues[0]; + assert.equal(issue.reason, "out_of_range"); + assert.equal(issue.path[0], "capture_path"); + assert.match(issue.path[1], /^\/_extension\/child/); + assert.equal( + issue.expected.maximum_capture_nesting, + WEB_NETWORK_CAPTURE_LIMITS.depth, + ); + cases++; + } + await inspect( + mode, + { + capture_path: join(runtime.path, "invalid-credential-key.mitm"), + format: "mitmproxy", + }, + "invalid_input", + ); + cases++; + const credentialDepth = await inspect( + mode, + { + capture_path: join(runtime.path, "deep-credential.mitm"), + format: "mitmproxy", + }, + "invalid_input", + ); + assert.equal(credentialDepth.details.issues[0].reason, "out_of_range"); + assert.match( + credentialDepth.details.issues[0].path[1], + /^\/request\/headers\/0\/1\/child/, + ); + cases++; + const value = await inspect(mode, { + capture_path: privatePath, + format: "har", + sensitive_values: ["REDACTED", "["], + }); + assert.equal(value.artifact.path, ""); + assert.ok(!JSON.stringify(value).includes("REDACTED")); + cases++; + await inspect( + mode, + { + capture_path: join(runtime.path, "invalid-private-parent.har"), + format: "har", + sensitive_values: ["response"], + }, + "invalid_input", + ); + cases++; + await inspect( + mode, + { + capture_path: join(runtime.path, "private-property-absent.har"), + format: "har", + sensitive_values: ["private-property", "REDACTED"], + }, + "invalid_input", + ); + cases++; + await inspect( + mode, + { + capture_path: join(runtime.path, "invalid-private-key.mitm"), + format: "mitmproxy", + sensitive_values: ["private-property"], + }, + "invalid_input", + ); + cases++; + } + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, { + capture_path: join(runtime.path, "flows.mitm"), + format: "mitmproxy", + record_ordinals: [0], + sensitive_values: ["body"], + }); + assert.equal( + value.records[0].binary_fields.find( + (field) => field.pointer === "/request/content", + ).state, + "redacted", + ); + cases++; + } + for (const format of ["har", "mitmproxy"]) { + const path = join(runtime.path, `empty-${format}`); + await writeFile( + path, + format === "har" + ? JSON.stringify({ + log: { + version: "1.2", + creator: { name: "source-owned-empty-fixture", version: "1" }, + entries: [], + }, + }) + : "", + ); + for (const mode of ["cli", "mcp"]) { + const value = await inspect(mode, { capture_path: path, format }); + assert.equal(value.total_records, 0); + cases++; + } + } +} catch (cause) { + failures.push(cause); +} finally { + for (const cleanup of [ + () => client.close(), + () => transport.close(), + () => runtime.close(), + ]) { + try { + await cleanup(); + } catch (cause) { + failures.push(cause); + } + } +} +const verifier = await completeVerifierRun(run); +try { + assert.equal(verifier.process_lineage.status, "verified"); + assert.deepEqual(verifier.process_lineage.descendants, []); +} catch (cause) { + failures.push(cause); +} +if (failures.length > 0) { + console.error( + JSON.stringify( + { status: "failed", public_cases: cases, verifier }, + null, + 2, + ), + ); + throw new AggregateError( + failures, + "Historical capture verification failed; analysis and cleanup failures are retained.", + ); +} +console.log( + JSON.stringify( + { + status: "passed", + public_cases: cases, + upstream: "mitmproxy 12.2.3 FlowWriter + SaveHar", + native_failure_classifications: 7, + offline: true, + verifier, + }, + null, + 2, + ), +); + +async function inspect(mode, input, errorCategory) { + if (mode === "mcp") { + const result = await client.callTool({ + name: "inspect_web_network_capture", + arguments: input, + }); + const value = JSON.parse(mcpTextValue(result)); + if (errorCategory !== undefined) { + assert.equal(result.isError, true); + assert.equal(value.error.category, errorCategory); + for (const literal of input.sensitive_values ?? []) + assert.ok(!JSON.stringify(value.error).includes(literal)); + return value.error; + } + assert.notEqual(result.isError, true, mcpTextValue(result)); + assertSensitiveLimitations(value.evidence, input.sensitive_values ?? []); + return value.result; + } + const args = [ + entrypoint, + "inspect-web-network-capture", + input.capture_path, + input.format, + "--json", + ...(input.record_ordinals ?? []).flatMap((ordinal) => [ + "--record", + String(ordinal), + ]), + ...(input.sensitive_values ?? []).flatMap((value) => [ + "--sensitive-value", + value, + ]), + ]; + let result; + try { + result = await promisify(execFile)(process.execPath, args, { + env: environment, + timeout: 40_000, + maxBuffer: 96 * 1024 * 1024, + }); + } catch (cause) { + if (errorCategory === undefined) throw cause; + assert.equal(typeof cause.code, "number"); + const value = JSON.parse(cause.stdout); + assert.equal(value.category, errorCategory); + for (const literal of input.sensitive_values ?? []) + assert.ok(!JSON.stringify(value).includes(literal)); + return value; + } + assert.equal(errorCategory, undefined, "Expected malformed capture to fail"); + const evidence = JSON.parse(result.stdout); + assertSensitiveLimitations(evidence, input.sensitive_values ?? []); + return evidence.normalized_result; +} + +function assertSensitiveLimitations(evidence, literals) { + for (const value of Object.values(evidence.parameters)) + if (typeof value === "string") + for (const literal of literals) + assert.ok( + !value.includes(literal), + "Declared literal retained in Evidence parameter", + ); + const result = evidence.normalized_result; + if (result.artifact.sha256 === null) assert.equal(evidence.subject, null); + else assert.equal(evidence.subject.digest.sha256, result.artifact.sha256); + assert.deepEqual( + evidence.provider, + result.decoder, + "Evidence provider tuple changed", + ); + assert.equal( + result.decoder.name, + result.format === "har" + ? "REA HAR capture adapter" + : "REA offline native mitmproxy adapter", + ); + const texts = [ + evidence.limitations, + result.limitations, + ...result.records.map((record) => record.limitations), + ].flat(); + for (const literal of literals) + assert.ok( + texts.every((text) => !text.includes(literal)), + "Declared literal retained in authored limitations", + ); +} + +function assertBinary(record, pointer, bytes) { + const field = record.binary_fields.find((item) => item.pointer === pointer); + assert.ok(field, `Missing native byte field ${pointer}`); + assert.equal(field.state, "retained"); + assert.equal(field.content_base64, bytes.toString("base64")); + assert.equal(field.bytes, bytes.length); + assert.equal(field.sha256, createHash("sha256").update(bytes).digest("hex")); +} diff --git a/scripts/verify-windows-native.mjs b/scripts/verify-windows-native.mjs index eb079dedb..7b95c8e4a 100644 --- a/scripts/verify-windows-native.mjs +++ b/scripts/verify-windows-native.mjs @@ -263,6 +263,80 @@ try { ]); native.call("runtime_snapshot_cancel", [runtime.handle]); await assert.rejects(cancelled, /cancelled/u); + assert.equal(await exists(join(runtime.path, "cancelled.bin")), false); + const readbackRuntime = WindowsPrivateRuntime.create( + workspace.replaceAll("\\", "/"), + "readback-", + ); + try { + readbackRuntime.writeFile("written.txt", "runtime-write-readback"); + assert.equal( + readbackRuntime.readFile("written.txt"), + "runtime-write-readback", + ); + const readbackSnapshot = await readbackRuntime.snapshot( + source, + "snapshot.bin", + ); + assert.equal( + readbackSnapshot.sha256, + createHash("sha256") + .update(await readFile(source)) + .digest("hex"), + ); + assert.equal( + readbackRuntime.readFile("snapshot.bin"), + await readFile(source, "utf8"), + ); + const writtenPath = join(readbackRuntime.observation.path, "written.txt"); + await assert.rejects(writeFile(writtenPath, "replace")); + await assert.rejects(rename(writtenPath, `${writtenPath}.renamed`)); + await assert.rejects(rm(writtenPath)); + assert.equal( + readbackRuntime.readFile("written.txt"), + "runtime-write-readback", + ); + const snapshotPath = join( + readbackRuntime.observation.path, + "snapshot.bin", + ); + // Java RandomAccessFile uses read/write sharing without delete sharing. + // Exercise that independent Win32 reader contract without requiring Java. + const readerDigest = execFileSync( + "powershell.exe", + [ + "-NoProfile", + "-NonInteractive", + "-Command", + "$ErrorActionPreference = 'Stop'; " + + "$stream = [IO.File]::Open($env:REA_SNAPSHOT_READER_PATH, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite); " + + "$hash = [Security.Cryptography.SHA256]::Create(); " + + "try { [BitConverter]::ToString($hash.ComputeHash($stream)).Replace('-', '').ToLowerInvariant() } " + + "finally { $hash.Dispose(); $stream.Dispose() }", + ], + { + encoding: "utf8", + timeout: 15_000, + env: { ...process.env, REA_SNAPSHOT_READER_PATH: snapshotPath }, + }, + ).trim(); + assert.equal(readerDigest, readbackSnapshot.sha256); + await assert.rejects(writeFile(snapshotPath, "replace")); + await assert.rejects(rename(snapshotPath, `${snapshotPath}.renamed`)); + await assert.rejects(rm(snapshotPath)); + assert.equal( + readbackRuntime.readFile("snapshot.bin"), + await readFile(source, "utf8"), + ); + report.controls = { + ...report.controls, + completedRuntimeFileReadback: true, + completedRuntimeFileMutationDenied: true, + snapshotReadWriteSharingReader: true, + }; + } finally { + await readbackRuntime.close(); + } report.controls = { ...report.controls, protectedDaclReadback: true, diff --git a/scripts/verify/javascript/output.mjs b/scripts/verify/javascript/output.mjs new file mode 100644 index 000000000..0e6d5635e --- /dev/null +++ b/scripts/verify/javascript/output.mjs @@ -0,0 +1,134 @@ +import assert from "node:assert/strict"; +import { constants } from "node:buffer"; +import { createHash } from "node:crypto"; +import { once } from "node:events"; +import { createReadStream, createWriteStream } from "node:fs"; +import { mkdtemp, rm, stat, statfs } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { finished } from "node:stream/promises"; + +import { Cli } from "incur"; + +import { createStreamedCliJsonOutput } from "../../../dist/cli/streamedJsonOutput.js"; +import { createEvidence } from "../../../dist/domain/evidence.js"; + +// This opt-in format-boundary check writes one temporary file larger than the +// actual engine string limit. Shared leaves keep the analysis input small. +const format = process.argv[2] ?? "json"; +assert.ok(format === "json" || format === "jsonl", "Use json or jsonl"); +const leaf = "x".repeat(64 * 1024); +const encodedLeaf = JSON.stringify(leaf); +const count = Math.ceil(constants.MAX_STRING_LENGTH / (leaf.length + 3)) + 1; +const volume = await statfs(tmpdir()); +assert.ok( + volume.bavail * volume.bsize > count * encodedLeaf.length + 1024 ** 3, + "Large-output verification needs room for one output file and a 1-GiB reserve", +); +const workspace = await mkdtemp(join(tmpdir(), "rea-javascript-output-")); +const path = join(workspace, "result.json"); +let destination; +let peakRss = process.memoryUsage().rss; +const sampleRss = () => { + peakRss = Math.max(peakRss, process.memoryUsage().rss); +}; +const sampler = setInterval(sampleRss, 25); +sampler.unref(); + +try { + const evidence = createEvidence( + undefined, + { id: "large-output-fixture", name: "Large output fixture", version: "1" }, + { + operation: "inspect_fixture", + parameters: {}, + result: { payload: Array.from({ length: count }, () => leaf) }, + }, + ); + sampleRss(); + const metadata = { command: "inspect-output-fixture", duration: "0ms" }; + const marker = "__REA_OUTPUT_ENCODING_MARKER__"; + const template = { + ok: true, + data: { ...evidence, normalized_result: { payload: [marker] } }, + meta: metadata, + }; + const skeleton = JSON.stringify(template, null, format === "json" ? 2 : 0); + const encodedMarker = JSON.stringify(marker); + const markerIndex = skeleton.indexOf(encodedMarker); + assert.ok(markerIndex >= 0); + assert.equal(markerIndex, skeleton.lastIndexOf(encodedMarker)); + const prefix = skeleton.slice(0, markerIndex); + const suffix = `${skeleton.slice(markerIndex + encodedMarker.length)}\n`; + const indentation = prefix.slice(prefix.lastIndexOf("\n") + 1); + const separator = format === "json" ? `,\n${indentation}` : ","; + const expectedHash = createHash("sha256"); + expectedHash.update(prefix); + for (let index = 0; index < count; index += 1) { + if (index > 0) expectedHash.update(separator); + expectedHash.update(encodedLeaf); + } + expectedHash.update(suffix); + const expectedBytes = + Buffer.byteLength(prefix) + + count * Buffer.byteLength(encodedLeaf) + + (count - 1) * Buffer.byteLength(separator) + + Buffer.byteLength(suffix); + assert.ok(expectedBytes > constants.MAX_STRING_LENGTH); + + destination = createWriteStream(path, { flags: "wx", mode: 0o600 }); + const argv = [metadata.command, "--format", format, "--full-output"]; + const output = createStreamedCliJsonOutput(argv, destination); + assert.ok(output); + let unexpectedOutputBytes = 0; + let exitCode = 0; + await Cli.create("rea-output-verifier", { sync: false }) + .command(metadata.command, { + run: async ({ format: selectedFormat }) => { + assert.equal( + await output.write(evidence, { ...metadata, format: selectedFormat }), + true, + ); + }, + }) + .serve(argv, { + stdout: (text) => { + if (!output.handled) unexpectedOutputBytes += Buffer.byteLength(text); + }, + exit: (code) => { + exitCode = code; + }, + }); + destination.end(); + await finished(destination); + assert.equal(exitCode, 0); + assert.equal(output.handled, true); + assert.equal(output.failed, false); + assert.equal(unexpectedOutputBytes, 0); + + const actualHash = createHash("sha256"); + for await (const chunk of createReadStream(path)) actualHash.update(chunk); + const digest = actualHash.digest("hex"); + assert.equal(digest, expectedHash.digest("hex")); + assert.equal((await stat(path)).size, expectedBytes); + sampleRss(); + process.stdout.write( + `${JSON.stringify({ + json_output_verified: true, + format, + output_bytes: expectedBytes, + engine_string_limit: constants.MAX_STRING_LENGTH, + output_sha256: digest, + evidence_id: evidence.evidence_id, + rss: { method: "node-memoryUsage-rss-sampled", peak_bytes: peakRss }, + })}\n`, + ); +} finally { + clearInterval(sampler); + if (destination !== undefined && !destination.closed) { + const closed = once(destination, "close"); + destination.destroy(); + await closed; + } + await rm(workspace, { recursive: true, force: true }); +} diff --git a/server.json b/server.json index 11161d5e7..116bde8aa 100644 --- a/server.json +++ b/server.json @@ -8,13 +8,13 @@ "url": "https://github.com/morluto/rea", "source": "github" }, - "version": "4.1.0", + "version": "5.0.0", "packages": [ { "registryType": "npm", "registryBaseUrl": "https://registry.npmjs.org", "identifier": "rea-agents", - "version": "4.1.0", + "version": "5.0.0", "runtimeHint": "npx", "transport": { "type": "stdio" diff --git a/skills/reverse-engineer-anything/SKILL.md b/skills/reverse-engineer-anything/SKILL.md index 9cfaa9118..3d4dac785 100644 --- a/skills/reverse-engineer-anything/SKILL.md +++ b/skills/reverse-engineer-anything/SKILL.md @@ -2,9 +2,9 @@ name: reverse-engineer-anything description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis. metadata: - version: "28" - tool_count: 134 - catalog_digest: "a9f3428463b46fece17dc43ad1aa913296e0b11cf3d57ce477e3b06e93b99d17" + version: "29" + tool_count: 135 + catalog_digest: "bfa2ed377826f8e561d1b8a9024d4ab0cf11f09d8b1af86c80d2884c1d72dbf3" --- # REA @@ -93,6 +93,11 @@ explicit path or endpoint and do not need it. consult the NativeAOT workflow in `references/native-and-artifacts.md` for optional metadata recovery and its supported host/layout boundary. - User-owned browser page already open: `list_browser_targets`. +- Retained HAR/native mitmproxy capture: `inspect_web_network_capture`. Select + original record ordinals when useful; inspect producer fields and byte/number + sidecars without fetching recorded URLs or claiming live attribution. See + [historical captures](https://github.com/morluto/rea/blob/main/docs/web-network-captures.md) for the exact upstream + profile and credential exclusions. - User-owned Electron runtime already open: `list_electron_targets`. - Native executable, library, or analysis database: `open_binary`, then use focused analysis tools directly; call `binary_overview` when metadata or diff --git a/src/android/JadxProvider.ts b/src/android/JadxProvider.ts index 31b43ebed..9c0be14d9 100644 --- a/src/android/JadxProvider.ts +++ b/src/android/JadxProvider.ts @@ -316,6 +316,7 @@ export class JadxProvider implements AndroidAnalysisPort { ], cwd: root.path, hostEnvironment: this.environment, + signal, }, this.launcher, ); diff --git a/src/application/ArtifactGraphConstruction.test.ts b/src/application/ArtifactGraphConstruction.test.ts deleted file mode 100644 index 1454a504c..000000000 --- a/src/application/ArtifactGraphConstruction.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { compareDirectoryChildNames } from "./ArtifactGraphConstruction.js"; - -const tiedNames = ["a.txt", "a\u200b.txt", "a\u200c.txt", "a\u200d.txt"]; - -describe("directory child-name ordering", () => { - it("orders the tied subset lexically with reflexivity and antisymmetry", () => { - for (const left of tiedNames) { - expect(compareDirectoryChildNames(left, left)).toBe(0); - for (const right of tiedNames) { - expect(left.localeCompare(right)).toBe(0); - const compared = compareDirectoryChildNames(left, right); - const lexical = left < right ? -1 : left > right ? 1 : 0; - expect(compared).toBe(lexical); - expect(compared + compareDirectoryChildNames(right, left)).toBe(0); - } - } - }); - - it("is transitive across the tied subset", () => { - for (const first of tiedNames) - for (const second of tiedNames) - for (const third of tiedNames) - if ( - compareDirectoryChildNames(first, second) <= 0 && - compareDirectoryChildNames(second, third) <= 0 - ) - expect( - compareDirectoryChildNames(first, third), - ).toBeLessThanOrEqual(0); - }); - - it.each([ - ["a.txt", "b.txt"], - ["z.txt", "ä.txt"], - ["Z", "a"], - ["a/data.txt", "b/data.txt"], - ])("preserves the current non-tied collation for %s / %s", (left, right) => { - const collated = left.localeCompare(right); - expect(collated).not.toBe(0); - expect(compareDirectoryChildNames(left, right)).toBe(collated); - expect(compareDirectoryChildNames(right, left)).toBe( - right.localeCompare(left), - ); - }); -}); diff --git a/src/application/ArtifactInventoryPolicy.test.ts b/src/application/ArtifactInventoryPolicy.test.ts deleted file mode 100644 index 66c89c0e4..000000000 --- a/src/application/ArtifactInventoryPolicy.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { resolveArtifactIntegrityPolicy } from "./ArtifactInventory/policy.js"; - -describe("artifact inventory policy resolution", () => { - it("preserves the selected integrity behavior without disabling verification", () => { - expect(resolveArtifactIntegrityPolicy({ mode: "fail" })).toEqual({ - mode: "fail", - }); - expect( - resolveArtifactIntegrityPolicy({ mode: "record-and-continue" }), - ).toEqual({ mode: "record-and-continue" }); - }); -}); diff --git a/src/application/AuthorizedArtifactInventory.ts b/src/application/AuthorizedArtifactInventory.ts deleted file mode 100644 index 71e2306a0..000000000 --- a/src/application/AuthorizedArtifactInventory.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { realpath } from "node:fs/promises"; - -import { - scanCanonicalArtifactInventory, - type ArtifactInventoryOptions, - type ArtifactInventorySnapshot, -} from "./ArtifactInventory.js"; - -/** Resolve, authorize, and scan one artifact without a second path resolution. */ -export const scanAuthorizedArtifactInventory = async ( - inputPath: string, - options: ArtifactInventoryOptions = {}, -): Promise => { - const path = await realpath(inputPath); - return scanCanonicalArtifactInventory(path, options); -}; diff --git a/src/application/CapabilityInventory.contract.test.ts b/src/application/CapabilityInventory.contract.test.ts deleted file mode 100644 index c9200f405..000000000 --- a/src/application/CapabilityInventory.contract.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { toolAvailability } from "../contracts/toolOutputSchemaPrimitives.js"; -import { buildCapabilityInventory } from "./CapabilityInventory.js"; - -describe("capability inventory contract", () => { - it("produces valid availability and rejects contradictory states", () => { - const inventory = buildCapabilityInventory( - { open: false, capabilities: [] }, - { - processCaptureEnabled: false, - }, - ); - const unavailable = inventory.find(({ available }) => !available); - expect(unavailable).toBeDefined(); - expect(toolAvailability.safeParse(unavailable).success).toBe(true); - expect( - toolAvailability.safeParse({ ...unavailable, available: true }).success, - ).toBe(false); - expect( - toolAvailability.safeParse({ - ...unavailable, - available: false, - reason: "available", - }).success, - ).toBe(false); - }); -}); diff --git a/src/application/CapabilityInventory.test.ts b/src/application/CapabilityInventory.test.ts index 2f960f15f..8ddf4f724 100644 --- a/src/application/CapabilityInventory.test.ts +++ b/src/application/CapabilityInventory.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; +import { toolAvailability } from "../contracts/toolOutputSchemaPrimitives.js"; import { buildCapabilityInventory } from "./CapabilityInventory.js"; it("reports module syntax and native resolution availability separately", () => { @@ -249,6 +250,23 @@ describe("capability inventory: caller guidance", () => { } }); + it("keeps the public availability schema consistent with generated states", () => { + const inventory = buildCapabilityInventory(status(), enabledPolicy); + const unavailable = inventory.find(({ available }) => !available); + expect(unavailable).toBeDefined(); + expect(toolAvailability.safeParse(unavailable).success).toBe(true); + expect( + toolAvailability.safeParse({ ...unavailable, available: true }).success, + ).toBe(false); + expect( + toolAvailability.safeParse({ + ...unavailable, + available: false, + reason: "available", + }).success, + ).toBe(false); + }); + it("does not require interactive elicitation for process capture", () => { const withoutElicitation = buildCapabilityInventory( status(), diff --git a/src/application/DirectAnalysis.ts b/src/application/DirectAnalysis.ts index a6c27834b..ea7ed8f6c 100644 --- a/src/application/DirectAnalysis.ts +++ b/src/application/DirectAnalysis.ts @@ -5,6 +5,8 @@ import { EnhancedTools } from "./EnhancedTools.js"; import { executeFunctionAnalysisEvidence } from "./FunctionAnalysisEvidence.js"; import type { DirectAnalysisDependencies } from "./DirectAnalysisDependencies.js"; import type { BinarySession } from "./binary/BinarySession.js"; +import type { SessionProviderRoute } from "./binary/SessionProviderRouter.js"; +import type { ResolvedSessionOpen } from "./binary/BinarySessionOpen.js"; import { silentLogger, type Logger } from "../logger.js"; import { createEvidence } from "../domain/evidence.js"; import type { Evidence } from "../domain/evidence.js"; @@ -28,10 +30,17 @@ import { snapshotEvidenceForQuery, snapshotMatchesTarget, } from "../domain/analysisSnapshot.js"; -import type { AnalysisProfileCommitment } from "../domain/analysisProfile.js"; +import { + analysisProfileSchema, + committedProviderSchema, + type AnalysisProfileCommitment, +} from "../domain/analysisProfile.js"; import { err, ok, type Result } from "../domain/result.js"; import type { AnalysisSnapshot } from "../domain/analysisSnapshot.js"; -import type { AnalysisExecution } from "./AnalysisProvider.js"; +import type { + AnalysisExecution, + ProviderIdentity, +} from "./AnalysisProvider.js"; import { REA_WORKFLOW_PROVIDER, workflowAnalysisProfile, @@ -194,7 +203,42 @@ const runAnalysis = async ( }); if (!prepared.ok) return cliError(prepared.error); const { snapshot } = prepared.value; - const opened = await session.open(path, { + let resolvedTarget: ResolvedSessionOpen | undefined; + if (snapshot !== undefined && prepared.value.target !== undefined) { + const preview = await session.previewTarget(prepared.value.target, { + signal, + snapshot, + ...(options.formatHint === undefined + ? {} + : { formatHint: options.formatHint }), + ...(options.providerId === undefined + ? {} + : { providerId: options.providerId }), + }); + if (!preview.ok) return cliError(preview.error); + resolvedTarget = preview.value; + const route = preview.value.route; + const bindingProfile = route.profile ?? undefined; + const evidenceProfile = analysisProfileForRoute(route, tool); + if ( + bindingProfile !== undefined && + evidenceProfile !== undefined && + allowsSnapshotReplay(route, tool) + ) { + const cached = snapshotEvidenceForQuery(snapshot, { + target: preview.value.target, + bindingProfile, + operation: tool, + parameters: arguments_, + provider: isWorkflowEvidenceTool(tool) + ? REA_WORKFLOW_PROVIDER + : providerIdentityForRoute(route, tool), + evidenceProfile, + }); + if (cached !== undefined) return cached; + } + } + const openOptions = { signal, ...(options.formatHint === undefined ? {} @@ -203,26 +247,13 @@ const runAnalysis = async ( ...(options.providerId === undefined ? {} : { providerId: options.providerId }), - }); + }; + const opened = + resolvedTarget === undefined + ? await session.open(path, openOptions) + : await session.openResolvedTarget(resolvedTarget, openOptions); if (!opened.ok) return cliError(opened.error); const evidenceProfile = analysisProfileForEvidence(session, tool); - const bindingProfile = session.analysisProfile(); - if ( - canReplayDirectTool(session, tool) && - snapshot !== undefined && - evidenceProfile !== undefined && - bindingProfile !== undefined - ) { - const cached = snapshotEvidenceForQuery(snapshot, { - target: opened.value, - bindingProfile, - operation: tool, - parameters: arguments_, - provider: replayProviderFor(session, tool), - evidenceProfile, - }); - if (cached !== undefined) return cached; - } const { output, evidence } = await executeAnalysisTool({ session, openedTarget: opened.value, @@ -232,6 +263,41 @@ const runAnalysis = async ( evidenceProfile, }); if (evidence !== undefined) session.recordEvidence(evidence); + if ( + isWorkflowEvidenceTool(tool) && + tool !== "trace_native_ui_action" && + snapshotPath !== undefined && + evidence !== undefined && + "analysis_profile" in evidence && + session.allowsSnapshotReplay(tool) + ) { + const recorded = session.recordWorkflowSnapshot({ + operation: tool, + parameters: arguments_, + execution: { + result: evidence.normalized_result, + rawResult: evidence.raw_result, + provider: committedProviderSchema.parse(evidence.provider), + analysisProfile: analysisProfileSchema.parse( + evidence.analysis_profile, + ), + limitations: evidence.limitations, + locations: evidence.locations, + subject: + evidence.subject === null + ? null + : { + path: evidence.subject.local_path, + sha256: evidence.subject.digest.sha256, + format: evidence.subject.format, + ...(evidence.subject.architecture === null + ? {} + : { architecture: evidence.subject.architecture }), + }, + }, + }); + if (!recorded.ok) return cliError(recorded.error); + } if ( tool !== "trace_native_ui_action" && snapshotPath !== undefined && @@ -252,12 +318,6 @@ const runAnalysis = async ( } }; -const canReplayDirectTool = ( - session: BinarySession, - tool: Parameters[2], -): boolean => - tool !== "trace_native_ui_action" && session.allowsSnapshotReplay(tool); - const executeAnalysisTool = async (input: { readonly session: BinarySession; readonly openedTarget: BinaryTarget; @@ -353,7 +413,10 @@ const prepareSnapshot = async (options: { readonly formatHint?: ExecutableFormatHint; readonly snapshotPath: string | undefined; }): Promise< - Result<{ readonly snapshot?: AnalysisSnapshot }, AnalysisError> + Result< + { readonly snapshot?: AnalysisSnapshot; readonly target?: BinaryTarget }, + AnalysisError + > > => { const { path, snapshotPath } = options; if (snapshotPath === undefined || !(await fileExists(snapshotPath))) @@ -374,20 +437,52 @@ const prepareSnapshot = async (options: { "Analysis snapshot target does not match the requested binary", ), ); - return ok({ snapshot: loaded.value }); + return ok({ snapshot: loaded.value, target: target.value }); }; -const replayProviderFor = ( - session: BinarySession, +const allowsSnapshotReplay = ( + route: SessionProviderRoute, tool: | NativeToolName | ArtifactAnalysisOperation | ManagedToolName | DirectAnalysisTool, -) => - isWorkflowEvidenceTool(tool) - ? REA_WORKFLOW_PROVIDER - : session.providerIdentity(tool); +): boolean => { + if (tool === "trace_native_ui_action") return false; + const descriptor = route.capabilities?.get(tool); + return descriptor === undefined + ? ![...(route.capabilities?.values() ?? [])].some( + ({ cachePolicy }) => cachePolicy === "live", + ) + : descriptor.cachePolicy !== "live"; +}; + +const analysisProfileForRoute = ( + route: SessionProviderRoute, + tool: + | NativeToolName + | ArtifactAnalysisOperation + | ManagedToolName + | DirectAnalysisTool, +): AnalysisProfileCommitment | undefined => { + const profile = route.profile; + if (profile === null || profile === undefined) return undefined; + if (isWorkflowEvidenceTool(tool)) return workflowAnalysisProfile(profile); + const provider = providerIdentityForRoute(route, tool); + return provider.id === profile.provider.id ? profile : undefined; +}; + +/** Mirror BinarySession.providerIdentity for a route not opened yet. */ +const providerIdentityForRoute = ( + route: SessionProviderRoute, + operation: string, +): ProviderIdentity => { + const selected = + route.capabilities?.get(operation)?.provider ?? route.identity; + return route.profile?.provider.id === selected.id + ? route.profile.provider + : selected; +}; const analysisProfileForEvidence = ( session: BinarySession, diff --git a/src/application/Doctor.test.ts b/src/application/Doctor.test.ts index 81e4009b4..af7881c5b 100644 --- a/src/application/Doctor.test.ts +++ b/src/application/Doctor.test.ts @@ -22,7 +22,7 @@ describe("doctor", () => { ), ).toEqual({ node: `Install ${SUPPORTED_NODE_VERSION_PROSE}.`, - host: "REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and the experimental Windows x64 Ghidra P0 boundary.", + host: "REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, CachyOS, and the experimental Windows x64 Ghidra P0 boundary.", hopper: "Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH.", target: "Supply a readable local app or program path.", }); diff --git a/src/application/DoctorDiagnostics.ts b/src/application/DoctorDiagnostics.ts index 1fe411e2d..0baf56b50 100644 --- a/src/application/DoctorDiagnostics.ts +++ b/src/application/DoctorDiagnostics.ts @@ -115,7 +115,7 @@ const hostCheck = async (host: DoctorHost): Promise => { : `${linuxDistribution.id} ${linuxDistribution.versionId ?? "unknown"}`); return check("host", supported, detail, { remediation: - "REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and the experimental Windows x64 Ghidra P0 boundary.", + "REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, CachyOS, and the experimental Windows x64 Ghidra P0 boundary.", classification: "unsupported_host", }); }; diff --git a/src/application/EnhancedLiteralTracing.test.ts b/src/application/EnhancedLiteralTracing.test.ts deleted file mode 100644 index 42e61974e..000000000 --- a/src/application/EnhancedLiteralTracing.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { ok } from "../domain/result.js"; -import { traceLiteralFeature } from "./EnhancedLiteralTracing.js"; - -describe("literal feature tracing", () => { - it("returns matched procedure and string facts with references", async () => { - const result = await traceLiteralFeature( - async (name) => { - if (name === "search_strings") - return ok([{ address: "0x1000", value: "launch feature" }]); - if (name === "search_procedures") - return ok([{ address: "0x2000", value: "launchFeature" }]); - if (name === "xrefs") return ok([]); - if (name === "resolve_containing_procedure") return ok(null); - throw new Error(`Unexpected analysis call: ${name}`); - }, - { query: "launch feature", case_sensitive: false }, - ); - expect(result).toMatchObject({ - ok: true, - value: { - matches: [ - { type: "string", address: "0x1000", value: "launch feature" }, - { type: "procedure", address: "0x2000", value: "launchFeature" }, - ], - references: [], - }, - }); - }); -}); diff --git a/src/application/EvidenceBundleFiles.ts b/src/application/EvidenceBundleFiles.ts index cf59d4109..8d9e1d665 100644 --- a/src/application/EvidenceBundleFiles.ts +++ b/src/application/EvidenceBundleFiles.ts @@ -8,7 +8,7 @@ import { EvidenceIntegrityError, } from "../domain/evidenceErrors.js"; import { err, ok, type Result } from "../domain/result.js"; -import { parseProcessCapture } from "../domain/processCapture.js"; +import { parseProcessCapture } from "../domain/process/processCapture.js"; import { readJsonFile, writeTextFile } from "./JsonFiles.js"; type EvidenceReadFailure = EvidenceFileError | EvidenceIntegrityError; diff --git a/src/application/LinuxHopper.test.ts b/src/application/LinuxHopper.test.ts index af2e05088..be115cc71 100644 --- a/src/application/LinuxHopper.test.ts +++ b/src/application/LinuxHopper.test.ts @@ -59,6 +59,7 @@ describe("Linux Hopper host classification", () => { ['ID=ubuntu\nVERSION_ID="24.04"\n', "deb"], ["ID=fedora\nVERSION_ID=41\n", "rpm"], ["ID=arch\n", "arch"], + ["ID=cachyos\nID_LIKE=arch\n", "arch"], ] as const)("accepts an official Hopper distribution", (document, family) => { expect(parseLinuxDistribution(document)).toMatchObject({ packageFamily: family, @@ -88,6 +89,8 @@ describe("Linux Hopper host classification", () => { 'ID=ubuntu\nVERSION_ID="22.04"\n', "ID=fedora\nVERSION_ID=40\n", 'ID=debian\nVERSION_ID="13"\nID_LIKE=debian\n', + "ID=manjaro\nID_LIKE=arch\n", + "ID=garuda\nID_LIKE=arch\n", ])("rejects unsupported vendor/version combinations", (document) => { expect(parseLinuxDistribution(document).supported).toBe(false); }); diff --git a/src/application/LinuxHopper.ts b/src/application/LinuxHopper.ts index 4840ac4d5..652c3d5b5 100644 --- a/src/application/LinuxHopper.ts +++ b/src/application/LinuxHopper.ts @@ -150,6 +150,7 @@ export const parseLinuxDistribution = (text: string): LinuxDistribution => { const supported = packageFamily !== undefined && (id === "arch" || + id === "cachyos" || (id === "ubuntu" && versionAtLeast(versionId, 24)) || (id === "fedora" && versionAtLeast(versionId, 41))); const identity = { diff --git a/src/application/ProcessCaptureLifecycle.ts b/src/application/ProcessCaptureLifecycle.ts deleted file mode 100644 index 1ea4b47e9..000000000 --- a/src/application/ProcessCaptureLifecycle.ts +++ /dev/null @@ -1,479 +0,0 @@ -import { createHash, randomUUID } from "node:crypto"; -import { createReadStream } from "node:fs"; -import { mkdtemp, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import type { IPty } from "@lydell/node-pty"; - -import type { - FilesystemCheckpoint, - InteractionEvent, - ProcessCapture, - UnverifiedProcessCapture, - ProcessSample, - ProcessSettlement, - ProcessScenario, - ProcessCaptureEventJournalEntry, - RecordProcessCaptureEvent, - TerminalFrame, -} from "../domain/processCapture.js"; -import { - digestProcessCommitment, - processComparisonContract, - processScenarioCommitment, -} from "../domain/processCapture.js"; -import { PRODUCT_IDENTITY } from "../identity.js"; -import type { SnapshotResult } from "./FilesystemSnapshot.js"; -import { snapshotRoots } from "./FilesystemSnapshot.js"; -import { classifyFilesystemEffects } from "./ProcessFilesystemEffects.js"; -import { cleanupOwnedProcessGroup } from "../process/ProcessOwnership.js"; -import { observeOwnedProcessGroup } from "../process/ProcessOwnershipObservation.js"; -import { ProcessCaptureError } from "./ProcessCaptureError.js"; -import { assertNotCancelled } from "./ProcessScenarioRuntimeValidation.js"; -import { - normalizeProcessElapsedTime, - normalizeProcessSamples, - normalizeProcessText, -} from "./ProcessNormalization.js"; -import { PROCESS_PROVIDER } from "./ProcessEvidence.js"; -import { TerminalRenderer } from "./TerminalRenderer.js"; -import { scheduleProcessInterval, type ProcessTimer } from "./ProcessTimer.js"; - -interface TerminalExitOptions { - readonly terminal: IPty; - readonly scenario: ProcessScenario; - readonly started: number; - readonly lastOutput: () => number; - readonly signal: AbortSignal | undefined; - readonly timers: Set; - readonly interactions: InteractionEvent[]; - readonly dispatchedEventIndexes: ReadonlySet; - readonly recordEvent: RecordProcessCaptureEvent; -} - -interface CaptureResultOptions { - readonly frames: readonly TerminalFrame[]; - readonly exit: { - readonly exitCode: number; - readonly signal?: number; - readonly reason: "exited" | "timeout" | "idle_timeout"; - }; - readonly samples: readonly ProcessSample[]; - readonly before: SnapshotResult; - readonly after: SnapshotResult; - readonly truncated: boolean; - readonly scenario: ProcessScenario; - readonly rootPid: number; - readonly samplingPartial: boolean; - readonly renderedFrames: UnverifiedProcessCapture["rendered_frames"]; - readonly interactions: readonly InteractionEvent[]; - readonly checkpoints: readonly FilesystemCheckpoint[]; - readonly settlement: ObservedProcessSettlement; - readonly manifest: UnverifiedProcessCapture["manifest"]; - readonly eventJournal: readonly ProcessCaptureEventJournalEntry[]; -} - -/** Settlement observation before process-resource cleanup has completed. */ -export type ObservedProcessSettlement = - | Pick< - Extract, - "state" | "elapsed_ms" - > - | Pick< - Exclude, - "state" | "elapsed_ms" - >; - -/** Capture observations before owned-resource cleanup has been verified. */ -export type PendingProcessCapture = Omit< - UnverifiedProcessCapture, - "cleanup" | "settlement" -> & { - readonly settlement: ObservedProcessSettlement; -}; - -/** Wait for trailing PTY callbacks before the capture becomes immutable. */ -export const settleProcessCaptureJournal = async ( - journal: readonly ProcessCaptureEventJournalEntry[], - quietMs = 25, - maxWaitMs = 500, -): Promise => { - const deadline = Date.now() + maxWaitMs; - let observed = journal.length; - let lastChangeAt = Date.now(); - while (Date.now() < deadline) { - await new Promise((resolve) => setTimeout(resolve, 4)); - if (journal.length !== observed) { - observed = journal.length; - lastChangeAt = Date.now(); - } else if (Date.now() - lastChangeAt >= quietMs) return; - } -}; - -export const buildCaptureResult = ( - options: CaptureResultOptions, -): PendingProcessCapture => { - const hasSensitiveScriptedInput = options.scenario.events.some( - (event) => event.type === "input" && event.sensitive, - ); - const sensitiveInputUnknown = - "Sensitive scripted input values are redacted from process capture Evidence."; - const filesystemObservationUnknown = - "No filesystem observation paths were selected; filesystem effects remain unknown."; - const hasFilesystemObservations = - options.scenario.filesystem_observation_paths.length > 0; - return { - manifest: options.manifest, - normalization: options.scenario.normalization, - frames: options.frames, - rendered_frames: options.renderedFrames, - interaction_events: options.interactions, - exit: { - code: - options.exit.reason === "exited" && options.exit.exitCode >= 0 - ? options.exit.exitCode - : null, - signal: options.exit.signal ?? null, - reason: options.exit.reason, - }, - settlement: options.settlement, - process_samples: normalizeProcessSamples( - options.samples, - options.scenario, - options.rootPid, - ), - filesystem_checkpoints: options.checkpoints, - event_journal: options.eventJournal, - files_before: options.before.files, - files_after: options.after.files, - filesystem_effects: classifyFilesystemEffects( - options.before.files, - options.after.files, - ), - truncated: options.truncated, - limitations: [ - "Process trees are sampled and may omit short-lived descendants.", - ...(options.samplingPartial - ? ["Process-tree sampling ended with an incomplete observation."] - : []), - "Filesystem observations are before/after snapshots, not syscall traces.", - "Inherited host environment variables are not recorded and may affect results.", - ...(!hasFilesystemObservations ? [filesystemObservationUnknown] : []), - ...(hasSensitiveScriptedInput ? [sensitiveInputUnknown] : []), - ], - residual_unknowns: [ - { - scope: "process", - reason: - "Process trees are sampled and may omit short-lived descendants.", - }, - { - scope: "environment", - reason: "Inherited host environment variables are not recorded.", - }, - { - scope: "network", - reason: "Network activity is not observed by this capture.", - }, - ...(!hasFilesystemObservations - ? [ - { - scope: "filesystem" as const, - reason: filesystemObservationUnknown, - }, - ] - : []), - ...(hasSensitiveScriptedInput - ? [{ scope: "interaction" as const, reason: sensitiveInputUnknown }] - : []), - ], - }; -}; - -const hashFile = async (path: string): Promise => { - const hash = createHash("sha256"); - for await (const chunk of createReadStream(path)) hash.update(chunk); - return hash.digest("hex"); -}; - -export const createRunManifest = async ( - scenario: ProcessScenario, - startedAt: Date, - completedAt: Date, - host: { - readonly platform: NodeJS.Platform; - readonly architecture: NodeJS.Architecture; - } = { - platform: process.platform, - architecture: process.arch, - }, -): Promise => { - const executableSha256 = await hashFile(scenario.executable); - const scenarioCommitment = processScenarioCommitment( - scenario, - executableSha256, - ); - const comparisonContract = processComparisonContract(scenario); - return { - rea_version: PRODUCT_IDENTITY.packageVersion, - provider_version: PROCESS_PROVIDER.version, - platform: host.platform, - architecture: host.architecture, - pty_backend: "node-pty", - started_at: startedAt.toISOString(), - completed_at: completedAt.toISOString(), - scenario: scenarioCommitment, - comparison_contract: comparisonContract, - full_scenario_sha256: digestProcessCommitment(scenarioCommitment), - comparison_contract_sha256: digestProcessCommitment(comparisonContract), - executable_sha256: executableSha256, - normalization_sha256: digestProcessCommitment(scenario.normalization), - }; -}; - -export const observeSettlement = async ( - runId: string, - processGroupIds: readonly number[], - settleMs: number, - recordEvent: RecordProcessCaptureEvent = () => undefined, - platform: NodeJS.Platform = process.platform, -): Promise => { - if (platform === "win32") { - recordEvent("lifecycle", 1); - return { state: "unverifiable", elapsed_ms: 0 }; - } - const started = Date.now(); - let consecutiveEmpty = 0; - let deadlineReached = false; - while (!deadlineReached) { - const observations = await Promise.all( - [...new Set(processGroupIds)].map((processGroupId) => - observeOwnedProcessGroup({ - runId, - leaderPid: processGroupId, - processGroupId, - }), - ), - ); - if (observations.some(({ state }) => state === "unverifiable")) { - recordEvent("lifecycle", 1); - return { state: "unverifiable", elapsed_ms: Date.now() - started }; - } - if (observations.every(({ state }) => state === "empty")) { - consecutiveEmpty += 1; - if (consecutiveEmpty >= 2) { - recordEvent("lifecycle", 1); - return { state: "quiesced", elapsed_ms: Date.now() - started }; - } - } else consecutiveEmpty = 0; - deadlineReached = Date.now() - started >= settleMs; - if (deadlineReached) break; - await new Promise((resolve) => setTimeout(resolve, 50)); - } - recordEvent("lifecycle", 1); - return { state: "alive_at_deadline", elapsed_ms: Date.now() - started }; -}; - -export const awaitTerminalExit = async ({ - terminal, - scenario, - started, - lastOutput, - signal, - timers, - interactions, - dispatchedEventIndexes, - recordEvent, -}: TerminalExitOptions): Promise<{ - exitCode: number; - signal?: number; - reason: "exited" | "timeout" | "idle_timeout" | "cancelled"; -}> => - new Promise((resolveExit) => { - // The kill caused by a deadline is observed later as an ordinary PTY exit. - // Keep the initiating lifecycle reason so comparisons distinguish a target - // exit from harness-owned timeout, idle-timeout, and cancellation cleanup. - let reason: "exited" | "timeout" | "idle_timeout" | "cancelled" = "exited"; - terminal.onExit((exit) => { - recordEvent("lifecycle", 0); - for (const [eventIndex, event] of scenario.events.entries()) { - if (dispatchedEventIndexes.has(eventIndex)) continue; - const sequence = interactions.length; - interactions.push({ - sequence, - scheduled_at_ms: event.at_ms, - dispatched_at_ms: Math.max(0, Date.now() - started), - type: event.type, - data: - event.type === "input" - ? "" - : event.type === "resize" - ? `${String(event.columns)}x${String(event.rows)}` - : event.signal, - outcome: "target_exited", - }); - recordEvent("interaction_events", sequence); - } - for (const timer of timers) { - timer.cancel(); - } - timers.clear(); - resolveExit({ ...exit, reason }); - }); - const timeout = scheduleProcessInterval(() => { - if (signal?.aborted === true) { - reason = "cancelled"; - terminal.kill("SIGKILL"); - } else if (Date.now() - started >= scenario.timeout_ms) { - reason = "timeout"; - terminal.kill("SIGKILL"); - } else if (Date.now() - lastOutput() >= scenario.idle_timeout_ms) { - reason = "idle_timeout"; - terminal.kill("SIGKILL"); - } - }, 20); - timers.add(timeout); - }); - -/** Host operations used when releasing one captured process run. */ -export interface ProcessCaptureCleanupHost { - readonly platform: NodeJS.Platform; - readonly cleanupProcessGroup: typeof cleanupOwnedProcessGroup; - readonly removeTemporaryRoot: (path: string) => Promise; -} - -const processCaptureCleanupHost: ProcessCaptureCleanupHost = { - platform: process.platform, - cleanupProcessGroup: cleanupOwnedProcessGroup, - removeTemporaryRoot: (path) => rm(path, { recursive: true, force: true }), -}; - -export const releaseProcessResources = async (options: { - readonly timers: ReadonlySet; - readonly terminal: IPty | undefined; - readonly renderer: TerminalRenderer | undefined; - readonly runId: string; - readonly temporaryRoot: string; - readonly capturedProcessGroupIds: readonly number[]; - readonly host?: ProcessCaptureCleanupHost; -}): Promise => { - const host = options.host ?? processCaptureCleanupHost; - for (const timer of options.timers) timer.cancel(); - let failure: string | undefined; - try { - await options.renderer?.dispose(); - } catch (cause: unknown) { - void cause; - failure ??= "terminal renderer cleanup failed"; - } - if (options.terminal !== undefined && host.platform !== "win32") { - for (const processGroupId of new Set(options.capturedProcessGroupIds)) { - const cleaned = await host.cleanupProcessGroup({ - runId: options.runId, - leaderPid: processGroupId, - processGroupId, - }); - if (!cleaned.cleaned) failure ??= cleaned.reason; - } - } - try { - await host.removeTemporaryRoot(options.temporaryRoot); - } catch (cause: unknown) { - void cause; - failure ??= "temporary process root cleanup failed"; - } - return failure; -}; - -export const captureTerminalFrames = (options: { - readonly terminal: IPty; - readonly scenario: ProcessScenario; - readonly frames: TerminalFrame[]; - readonly started: number; - readonly temporaryRoot: string; - readonly onOutput: () => void; - readonly renderer: TerminalRenderer; - readonly recordEvent: RecordProcessCaptureEvent; -}): (() => boolean) => { - let outputBytes = 0; - let truncated = false; - options.terminal.onData((data) => { - options.onOutput(); - const bytes = Buffer.byteLength(data); - if (outputBytes + bytes > options.scenario.limits.output_bytes) { - truncated = true; - return; - } - outputBytes += bytes; - const atMs = normalizeProcessElapsedTime( - Date.now() - options.started, - options.scenario.normalization.time_bucket_ms, - ); - const normalized = normalizeProcessText( - data, - options.scenario, - options.temporaryRoot, - options.terminal.pid, - ); - const sequence = options.frames.length; - options.frames.push({ - sequence, - at_ms: atMs, - data: normalized, - }); - options.renderer.write(data, atMs); - options.recordEvent("frames", sequence); - }); - return () => truncated; -}; - -export const resolveProcessResult = ( - capture: ProcessCapture | undefined, - executionFailure: unknown, - cleanupFailure: string | undefined, -): ProcessCapture => { - if (executionFailure instanceof ProcessCaptureError) throw executionFailure; - if (executionFailure !== undefined) - throw new ProcessCaptureError("process capture failed", { - cause: executionFailure, - }); - if (cleanupFailure !== undefined) - throw new ProcessCaptureError(cleanupFailure, { - reason: "cleanup_incomplete", - }); - if (capture === undefined) - throw new ProcessCaptureError("process capture produced no result"); - return capture; -}; - -export const prepareProcessCapture = async ( - scenario: ProcessScenario, - signal: AbortSignal | undefined, - host: ProcessPreparationHost = systemProcessPreparationHost, -): Promise<{ - readonly temporaryRoot: string; - readonly runId: string; - readonly before: SnapshotResult; -}> => { - assertNotCancelled(signal); - const before = await snapshotRoots(scenario, signal); - const temporaryRoot = await host.createTemporaryRoot(); - try { - const runId = randomUUID(); - return { temporaryRoot, runId, before }; - } catch (cause: unknown) { - await host.cleanup(temporaryRoot); - throw cause; - } -}; - -/** Filesystem seam for allocating and cleaning a capture's temporary root. */ -export interface ProcessPreparationHost { - createTemporaryRoot(): Promise; - cleanup(path: string): Promise; -} - -const systemProcessPreparationHost: ProcessPreparationHost = { - createTemporaryRoot: () => mkdtemp(join(tmpdir(), "rea-process-")), - cleanup: (path) => rm(path, { recursive: true, force: true }), -}; diff --git a/src/application/ProcessHarness.test.ts b/src/application/ProcessHarness.test.ts deleted file mode 100644 index 8c2d38bfc..000000000 --- a/src/application/ProcessHarness.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { expect, it } from "vitest"; -import { processScenarioSchema } from "../domain/processScenario.js"; -import { AnalysisCapabilityUnavailableError } from "../domain/analysisErrorCore.js"; -import { captureProcessScenario } from "./ProcessHarness.js"; -import { settleProcessCaptureJournal } from "./ProcessCaptureLifecycle.js"; -import { processCaptureSchema } from "../domain/processCapture.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js"; -import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; - -it("rejects legacy replay output instead of silently discarding it", () => { - expect( - processCaptureSchema.safeParse({ - ...EMPTY_PROCESS_CAPTURE_EXAMPLE, - protocol_events: [], - }).success, - ).toBe(false); -}); - -it("waits for terminal observations delivered after the exit callback", async () => { - const journal: Array<{ - capture_order: number; - collection: "frames"; - index: number; - }> = [{ capture_order: 0, collection: "frames", index: 0 }]; - const lateFrame = new Promise((resolve) => { - setTimeout(() => { - journal.push({ capture_order: 1, collection: "frames", index: 1 }); - resolve(); - }, 12); - }); - - let settled = false; - const wait = settleProcessCaptureJournal(journal, 20, 200).then(() => { - settled = true; - }); - await lateFrame; - expect(settled).toBe(false); - await wait; - - expect(journal).toHaveLength(2); - expect(journal[1]).toMatchObject({ collection: "frames", index: 1 }); -}); - -it("fails closed on Windows before resolving or launching scenario paths", async () => { - const scenario = processScenarioSchema.parse({ - executable: "Z:/missing/should-never-be-resolved.exe", - working_directory: "Z:/missing/working-directory", - }); - const result = await captureProcessScenario(scenario, undefined, "win32"); - expect(result.ok).toBe(false); - if (result.ok) throw new Error("expected Windows ownership refusal"); - if (!(result.error instanceof AnalysisCapabilityUnavailableError)) - throw new Error("expected a capability-unavailable outcome"); - expect(result.error).toMatchObject({ - operation: "capture_process_scenario", - reason: expect.stringContaining("Windows PTY process capture"), - }); - expect(projectAnalysisError(result.error)).toMatchObject({ - code: "capability_unavailable", - category: "unsupported_provider", - message: expect.stringContaining("does not yet verify descendant cleanup"), - details: { - operation: "capture_process_scenario", - reason: result.error.reason, - }, - }); -}); diff --git a/src/application/ProcessNormalization.test.ts b/src/application/ProcessNormalization.test.ts deleted file mode 100644 index 9baf1e4b6..000000000 --- a/src/application/ProcessNormalization.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { expect, it } from "vitest"; - -import { normalizeProcessSamples } from "./ProcessNormalization.js"; -import { - parseProcessScenario, - type ProcessSample, -} from "../domain/processCapture.js"; - -const rootPid = 41_010; -const samples = Object.freeze( - [ - { - at_ms: 17, - pid: rootPid, - parent_pid: 40_000, - process_group_id: rootPid, - session_id: rootPid, - command: " root 41010 ", - }, - { - at_ms: 28, - pid: 42_020, - parent_pid: rootPid, - process_group_id: 43_030, - session_id: 44_040, - command: "child 42020 parent 41010 group 43030 session 44040", - }, - { - at_ms: 39, - pid: 45_050, - parent_pid: 42_020, - process_group_id: 43_030, - session_id: 44_040, - command: "peer 45050 unrelated 145050", - }, - { - at_ms: 41, - pid: 46_060, - parent_pid: 0, - process_group_id: null, - session_id: 0, - command: "unknown 46060", - }, - { - at_ms: 52, - pid: 47_070, - parent_pid: 0, - process_group_id: 0, - session_id: null, - command: "unavailable 47070", - }, - ].map((sample) => - Object.freeze({ ...sample, runtime_metadata: "must not be serialized" }), - ), -); - -it.each([false, true, undefined])( - "projects only declared sample fields without mutating inputs when pids is %s", - (pids) => { - const scenario = parseProcessScenario({ - executable: "/usr/bin/node", - ...(pids === undefined ? {} : { normalization: { pids } }), - }); - const before = JSON.stringify(samples); - const normalized = normalizeProcessSamples(samples, scenario, rootPid); - const identities = - pids === false - ? [ - [rootPid, 40_000, rootPid, rootPid], - [42_020, rootPid, 43_030, 44_040], - [45_050, 42_020, 43_030, 44_040], - [46_060, 0, null, 0], - [47_070, 0, 0, null], - ] - : [ - [1, 2, 1, 1], - [3, 1, 4, 5], - [6, 3, 4, 5], - [7, 0, null, 0], - [8, 0, 0, null], - ]; - const commands = - pids === false - ? samples.map(({ command }) => command.trim()) - : [ - "root ", - "child parent group session ", - "peer unrelated 145050", - "unknown ", - "unavailable ", - ]; - - expect(scenario.normalization.pids).toBe(pids ?? true); - expect(normalized).toEqual( - identities.map( - ([pid, parent_pid, process_group_id, session_id], index) => ({ - at_ms: (index + 1) * 10, - pid, - parent_pid, - process_group_id, - session_id, - command: commands[index], - }), - ), - ); - expect(normalized).not.toBe(samples); - for (const [index, sample] of normalized.entries()) { - expect(sample).not.toBe(samples[index]); - expect(Object.keys(sample).sort()).toEqual( - [ - "at_ms", - "pid", - "parent_pid", - "process_group_id", - "session_id", - "command", - ].sort(), - ); - } - expect(JSON.stringify(samples)).toBe(before); - }, -); - -for (const pids of [false, true]) { - for (const paths of [false, true]) { - it.each([false, true])( - `keeps time, patterns, paths=${String(paths)}, and ports=%s independent of pids=${String(pids)}`, - (ports) => { - const sample: ProcessSample = Object.freeze({ - at_ms: 29, - pid: 42_020, - parent_pid: rootPid, - process_group_id: 43_030, - session_id: 44_040, - command: - " /workspace/program child 42020 parent 41010 group 43030 session 44040 listen:8080 marker ", - }); - const scenario = parseProcessScenario({ - executable: "/usr/bin/node", - working_directory: "/workspace", - normalization: { - pids, - paths, - ports, - time_bucket_ms: 25, - patterns: [{ pattern: "marker", replacement: "selected" }], - }, - }); - const normalized = normalizeProcessSamples([sample], scenario, rootPid); - const directory = paths ? "" : "/workspace"; - const identifiers = pids - ? "child parent group session " - : "child 42020 parent 41010 group 43030 session 44040"; - expect(normalized).toEqual([ - { - at_ms: 25, - pid: pids ? 2 : 42_020, - parent_pid: pids ? 1 : rootPid, - process_group_id: pids ? 3 : 43_030, - session_id: pids ? 4 : 44_040, - command: `${directory}/program ${identifiers} listen:${ports ? "" : "8080"} selected`, - }, - ]); - }, - ); - } -} diff --git a/src/application/ReconstructionObligationCandidates.test.ts b/src/application/ReconstructionObligationCandidates.test.ts index d3ddc3224..0644cf4d7 100644 --- a/src/application/ReconstructionObligationCandidates.test.ts +++ b/src/application/ReconstructionObligationCandidates.test.ts @@ -2,6 +2,14 @@ import { describe, expect, it } from "vitest"; import { createEvidence } from "../domain/evidence.js"; import { createEvidenceBundle } from "../domain/evidenceBundle.js"; +import { emptyUnverifiedProcessCapture } from "../domain/process/processCapture.fixture.js"; +import { parseProcessCapture } from "../domain/process/processCapture.js"; +import { + digestProcessCommitment, + parseProcessScenario, + processComparisonContract, + processScenarioCommitment, +} from "../domain/process/processScenario.js"; import { APPLICATION_GRAPH_DIGESTS, artifactEvidence, @@ -14,6 +22,7 @@ import { } from "../domain/javascript/javascriptApplicationGraph.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { MANAGED_WORKFLOW_PROVIDER } from "./InvestigationProviders.js"; +import { createProcessCaptureEvidence } from "./process/ProcessEvidence.js"; import { deriveReconstructionObligationCandidates } from "./ReconstructionObligationCandidates.js"; const applicationWithUnknowns = (keys: readonly string[]) => { @@ -105,30 +114,13 @@ const unknownLimitation = (graphId: string, nodeId: string): string => `Application graph ${graphId} contains unresolved node ${nodeId}; reconstruction obligations remain unknown.`; describe("reconstruction obligation candidates", () => { - it("preserves admitted unknown application nodes as limitations", () => { - const { graph, unknownNodes, generated } = applicationWithUnknowns([ - "unclassified-boundary", - ]); - const unknownNode = unknownNodes[0]; - if (unknownNode === undefined) throw new Error("Expected one unknown node"); - - expect(generated.candidates.length).toBeGreaterThan(0); - expect( - generated.candidates.some( - ({ target }) => target.application_node_id === unknownNode.node_id, - ), - ).toBe(false); - expect(generated.limitations).toEqual([ - unknownLimitation(graph.graph_id, unknownNode.node_id), - ]); - }); - - it("emits one deterministic limitation per unknown node", () => { + it("preserves unresolved application nodes as deterministic limitations", () => { const { graph, unknownNodes, generated } = applicationWithUnknowns([ "unclassified-first", "unclassified-second", ]); + expect(generated.candidates.length).toBeGreaterThan(0); expect([...generated.limitations].sort()).toEqual( unknownNodes .map((node) => unknownLimitation(graph.graph_id, node.node_id)) @@ -141,4 +133,41 @@ describe("reconstruction obligation candidates", () => { ), ).toBe(false); }); + + it("does not bind an unknown process image to a packaged artifact obligation", () => { + const scenario = parseProcessScenario({ + executable: "/path/whose-launch-identity-is-unknown", + }); + const base = emptyUnverifiedProcessCapture(); + const scenarioProjection = processScenarioCommitment(scenario); + const comparisonContract = processComparisonContract(scenario); + const capture = parseProcessCapture({ + ...base, + normalization: scenario.normalization, + manifest: { + ...base.manifest, + scenario: scenarioProjection, + comparison_contract: comparisonContract, + full_scenario_sha256: digestProcessCommitment(scenarioProjection), + comparison_contract_sha256: digestProcessCommitment(comparisonContract), + selected_executable_sha256: null, + executable_sha256: null, + executable_identity: { + state: "unknown", + reason: "Selected executable could not be inspected.", + }, + normalization_sha256: digestProcessCommitment(scenario.normalization), + }, + }); + const evidence = createProcessCaptureEvidence(scenario, capture); + const generated = deriveReconstructionObligationCandidates( + createEvidenceBundle([evidence]), + [], + ); + + expect(generated.candidates).toEqual([]); + expect(generated.limitations).toContain( + `Process capture ${evidence.evidence_id} has no validated executable artifact identity; packaged-process lifecycle obligation was not bound to an artifact.`, + ); + }); }); diff --git a/src/application/ReconstructionObligationCandidates.ts b/src/application/ReconstructionObligationCandidates.ts index e97764867..014037692 100644 --- a/src/application/ReconstructionObligationCandidates.ts +++ b/src/application/ReconstructionObligationCandidates.ts @@ -7,7 +7,7 @@ import { parseEvidenceBundle, type EvidenceBundle, } from "../domain/evidenceBundle.js"; -import { parseProcessCapture } from "../domain/processCapture.js"; +import { parseProcessCapture } from "../domain/process/processCapture.js"; import type { ReconstructionObligation, ReviewedReconstructionObligation, @@ -176,6 +176,12 @@ const deriveProcessCandidate = ( ] : []), ]; + if (capture.manifest.executable_sha256 === null) { + limitations.add( + `Process capture ${evidence.evidence_id} has no validated executable artifact identity; packaged-process lifecycle obligation was not bound to an artifact.`, + ); + return; + } addCandidate(candidates, { obligation_id: obligationId( `process:${capture.manifest.executable_sha256}:${capture.manifest.full_scenario_sha256}`, diff --git a/src/application/ReconstructionObligationLedger.authority.test.ts b/src/application/ReconstructionObligationLedger.authority.test.ts index b4a3f84ce..401088add 100644 --- a/src/application/ReconstructionObligationLedger.authority.test.ts +++ b/src/application/ReconstructionObligationLedger.authority.test.ts @@ -1,112 +1,16 @@ import { describe, expect, it } from "vitest"; -import { PROCESS_PROVIDER } from "./ProcessEvidence.js"; import { - buildReconstructionObligationLedgerEvidenceValidated, - resolveReconstructionObligationLedgerRequest, -} from "./ReconstructionObligationLedgerService.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js"; -import { createEvidence, type Evidence } from "../domain/evidence.js"; -import { createEvidenceBundle } from "../domain/evidenceBundle.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; -import { processCaptureSchema } from "../domain/processCapture.js"; -import { - reconstructionObligationLedgerSchema, - type ReconstructionObligationLedgerInput, - type ReconstructionObligationLedger, - type ReviewedReconstructionObligation, -} from "../domain/reconstructionObligationLedgerSchemas.js"; - -const proofEvidence = (id: string) => - createEvidence( - undefined, - { id: `fixture-${id}`, name: "Fixture verifier", version: "1" }, - { - predicateType: "rea.fixture-verification", - operation: "run_fixture_verifier", - parameters: { id }, - result: { passed: true }, - confidence: "observed", - authority: "controlled-replay", - }, - ); - -const processEvidence = () => - createEvidence(undefined, PROCESS_PROVIDER, { - predicateType: "rea.process-capture", - operation: "capture_process_scenario", - parameters: {}, - result: jsonValueSchema.parse( - processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE), - ), - confidence: "observed", - authority: "controlled-replay", - }); - -const boundProofEvidence = ( - id: string, - result: { - readonly obligation_ids: readonly string[]; - readonly fixture_ids: readonly string[]; - readonly case_kinds: readonly string[]; - readonly verifier_ids: readonly string[]; - readonly claim_ids: readonly string[]; - }, - authority: "shipped-artifact" | "controlled-replay" = "controlled-replay", -) => - createEvidence( - undefined, - { id: `proof-${id}`, name: "Reconstruction proof", version: "1" }, - { - predicateType: "rea.reconstruction-proof", - operation: "verify_reconstruction_obligations", - parameters: {}, - result: { - passed: true, - obligation_ids: [...result.obligation_ids], - fixture_ids: [...result.fixture_ids], - case_kinds: [...result.case_kinds], - verifier_ids: [...result.verifier_ids], - claim_ids: [...result.claim_ids], - }, - confidence: "observed", - authority, - }, - ); - -const request = ( - records: readonly Evidence[], - overrides: Partial = {}, -): ReconstructionObligationLedgerInput => ({ - evidence_bundle: createEvidenceBundle(records), - reviewed_obligations: [], - manifest: { bindings: [], contradictions: [] }, - ...overrides, -}); - -const build = ( - input: ReconstructionObligationLedgerInput, -): ReconstructionObligationLedger => { - const parsed = resolveReconstructionObligationLedgerRequest(input); - if (!parsed.ok) throw parsed.error; - const result = buildReconstructionObligationLedgerEvidenceValidated( - parsed.value, - ); - if (!result.ok) throw result.error; - return reconstructionObligationLedgerSchema.parse( - result.value.normalized_result, - ); -}; - -const originalCases = ( - obligation: ReconstructionObligationLedger["obligations"][number], - evidenceId: string, -) => - obligation.required_case_kinds.map((caseKind) => ({ - kind: caseKind, - evidence_id: evidenceId, - location: `/normalized_result/cases/${caseKind}`, - })); + boundProofEvidence, + build, + originalCases, + processEvidence, + proofEvidence, + request, + reviewedObligation, +} from "./ReconstructionObligationLedger.fixture.js"; +import { createEvidence } from "../domain/evidence.js"; +import type { ReviewedReconstructionObligation } from "../domain/reconstructionObligationLedgerSchemas.js"; describe("reconstruction obligation ledger", () => { it("does not authenticate packaged-process proof with generic replay evidence", () => { @@ -315,32 +219,3 @@ describe("reconstruction obligation ledger fail-closed behavior", () => { ]); }); }); - -const reviewedObligation = ( - obligationId: string, - evidenceId: string, -): ReviewedReconstructionObligation => ({ - obligation_id: obligationId, - obligation_version: 1, - title: `Reviewed obligation ${obligationId}`, - application_layer: "other", - family: "reviewed", - target: { - artifact_sha256: null, - application_node_id: null, - semantic_node_id: null, - location: `/reviewed/${obligationId}`, - }, - required: true, - required_case_kinds: ["positive"], - required_original_authority: "static", - required_fixture_authority: "unit", - required_verifier_authority: "unit", - requires_parser_type: false, - dependency_obligation_ids: [], - residual_unknown_ids: [], - unavailable_authority: [], - required_next_evidence: ["Bind an owner and verifier."], - disposition: "active", - review_evidence_ids: [evidenceId], -}); diff --git a/src/application/ReconstructionObligationLedger.closure.test.ts b/src/application/ReconstructionObligationLedger.closure.test.ts index f2bcd43d3..e11bffadd 100644 --- a/src/application/ReconstructionObligationLedger.closure.test.ts +++ b/src/application/ReconstructionObligationLedger.closure.test.ts @@ -1,141 +1,14 @@ import { describe, expect, it } from "vitest"; -import { PROCESS_PROVIDER } from "./ProcessEvidence.js"; import { - buildReconstructionObligationLedgerEvidenceValidated, - resolveReconstructionObligationLedgerRequest, -} from "./ReconstructionObligationLedgerService.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js"; -import { createEvidence, type Evidence } from "../domain/evidence.js"; -import { createEvidenceBundle } from "../domain/evidenceBundle.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; -import { processCaptureSchema } from "../domain/processCapture.js"; -import { - reconstructionObligationLedgerSchema, - type ReconstructionObligationLedgerInput, - type ReconstructionObligationLedger, - type ReviewedReconstructionObligation, -} from "../domain/reconstructionObligationLedgerSchemas.js"; - -const proofEvidence = (id: string) => - createEvidence( - undefined, - { id: `fixture-${id}`, name: "Fixture verifier", version: "1" }, - { - predicateType: "rea.fixture-verification", - operation: "run_fixture_verifier", - parameters: { id }, - result: { passed: true }, - confidence: "observed", - authority: "controlled-replay", - }, - ); - -const processEvidence = () => - createEvidence(undefined, PROCESS_PROVIDER, { - predicateType: "rea.process-capture", - operation: "capture_process_scenario", - parameters: {}, - result: jsonValueSchema.parse( - processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE), - ), - confidence: "observed", - authority: "controlled-replay", - }); - -const boundProofEvidence = ( - id: string, - result: { - readonly obligation_ids: readonly string[]; - readonly fixture_ids: readonly string[]; - readonly case_kinds: readonly string[]; - readonly verifier_ids: readonly string[]; - readonly claim_ids: readonly string[]; - }, - authority: "shipped-artifact" | "controlled-replay" = "controlled-replay", -) => - createEvidence( - undefined, - { id: `proof-${id}`, name: "Reconstruction proof", version: "1" }, - { - predicateType: "rea.reconstruction-proof", - operation: "verify_reconstruction_obligations", - parameters: {}, - result: { - passed: true, - obligation_ids: [...result.obligation_ids], - fixture_ids: [...result.fixture_ids], - case_kinds: [...result.case_kinds], - verifier_ids: [...result.verifier_ids], - claim_ids: [...result.claim_ids], - }, - confidence: "observed", - authority, - }, - ); - -const request = ( - records: readonly Evidence[], - overrides: Partial = {}, -): ReconstructionObligationLedgerInput => ({ - evidence_bundle: createEvidenceBundle(records), - reviewed_obligations: [], - manifest: { bindings: [], contradictions: [] }, - ...overrides, -}); - -const build = ( - input: ReconstructionObligationLedgerInput, -): ReconstructionObligationLedger => { - const parsed = resolveReconstructionObligationLedgerRequest(input); - if (!parsed.ok) throw parsed.error; - const result = buildReconstructionObligationLedgerEvidenceValidated( - parsed.value, - ); - if (!result.ok) throw result.error; - return reconstructionObligationLedgerSchema.parse( - result.value.normalized_result, - ); -}; - -const originalCases = ( - obligation: ReconstructionObligationLedger["obligations"][number], - evidenceId: string, -) => - obligation.required_case_kinds.map((caseKind) => ({ - kind: caseKind, - evidence_id: evidenceId, - location: `/normalized_result/cases/${caseKind}`, - })); - -const reviewedObligation = ( - obligationId: string, - evidenceId: string, -): ReviewedReconstructionObligation => ({ - obligation_id: obligationId, - obligation_version: 1, - title: `Reviewed obligation ${obligationId}`, - application_layer: "other", - family: "reviewed", - target: { - artifact_sha256: null, - application_node_id: null, - semantic_node_id: null, - location: `/reviewed/${obligationId}`, - }, - required: true, - required_case_kinds: ["positive"], - required_original_authority: "static", - required_fixture_authority: "unit", - required_verifier_authority: "unit", - requires_parser_type: false, - dependency_obligation_ids: [], - residual_unknown_ids: [], - unavailable_authority: [], - required_next_evidence: ["Bind an owner and verifier."], - disposition: "active", - review_evidence_ids: [evidenceId], -}); + boundProofEvidence, + build, + originalCases, + processEvidence, + proofEvidence, + request, + reviewedObligation, +} from "./ReconstructionObligationLedger.fixture.js"; describe("reconstruction obligation ledger", () => { it("emits the complete deterministic ledger and closure identity", () => { diff --git a/src/application/ReconstructionObligationLedger.fixture.ts b/src/application/ReconstructionObligationLedger.fixture.ts new file mode 100644 index 000000000..1892a4442 --- /dev/null +++ b/src/application/ReconstructionObligationLedger.fixture.ts @@ -0,0 +1,143 @@ +import { PROCESS_PROVIDER } from "./process/ProcessEvidence.js"; +import { + buildReconstructionObligationLedgerEvidenceValidated, + resolveReconstructionObligationLedgerRequest, +} from "./ReconstructionObligationLedgerService.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/process/processCapture.fixture.js"; +import { createEvidence, type Evidence } from "../domain/evidence.js"; +import { createEvidenceBundle } from "../domain/evidenceBundle.js"; +import { jsonValueSchema } from "../domain/jsonValue.js"; +import { processCaptureSchema } from "../domain/process/processCapture.js"; +import { + reconstructionObligationLedgerSchema, + type ReconstructionObligationLedgerInput, + type ReconstructionObligationLedger, + type ReviewedReconstructionObligation, +} from "../domain/reconstructionObligationLedgerSchemas.js"; + +/** Build controlled fixture Evidence used by reconstruction-ledger scenarios. */ +export const proofEvidence = (id: string) => + createEvidence( + undefined, + { id: `fixture-${id}`, name: "Fixture verifier", version: "1" }, + { + predicateType: "rea.fixture-verification", + operation: "run_fixture_verifier", + parameters: { id }, + result: { passed: true }, + confidence: "observed", + authority: "controlled-replay", + }, + ); + +/** Use the canonical empty process capture as a controlled Evidence example. */ +export const processEvidence = () => + createEvidence(undefined, PROCESS_PROVIDER, { + predicateType: "rea.process-capture", + operation: "capture_process_scenario", + parameters: {}, + result: jsonValueSchema.parse( + processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE), + ), + confidence: "observed", + authority: "controlled-replay", + }); + +/** Build verifier Evidence bound to enumerated fixture and obligation identities. */ +export const boundProofEvidence = ( + id: string, + result: { + readonly obligation_ids: readonly string[]; + readonly fixture_ids: readonly string[]; + readonly case_kinds: readonly string[]; + readonly verifier_ids: readonly string[]; + readonly claim_ids: readonly string[]; + }, + authority: "shipped-artifact" | "controlled-replay" = "controlled-replay", +) => + createEvidence( + undefined, + { id: `proof-${id}`, name: "Reconstruction proof", version: "1" }, + { + predicateType: "rea.reconstruction-proof", + operation: "verify_reconstruction_obligations", + parameters: {}, + result: { + passed: true, + obligation_ids: [...result.obligation_ids], + fixture_ids: [...result.fixture_ids], + case_kinds: [...result.case_kinds], + verifier_ids: [...result.verifier_ids], + claim_ids: [...result.claim_ids], + }, + confidence: "observed", + authority, + }, + ); + +/** Build a ledger request with empty reviewed input and manifest defaults. */ +export const request = ( + records: readonly Evidence[], + overrides: Partial = {}, +): ReconstructionObligationLedgerInput => ({ + evidence_bundle: createEvidenceBundle(records), + reviewed_obligations: [], + manifest: { bindings: [], contradictions: [] }, + ...overrides, +}); + +/** Run request resolution, Evidence validation, and output parsing for a fixture. */ +export const build = ( + input: ReconstructionObligationLedgerInput, +): ReconstructionObligationLedger => { + const parsed = resolveReconstructionObligationLedgerRequest(input); + if (!parsed.ok) throw parsed.error; + const result = buildReconstructionObligationLedgerEvidenceValidated( + parsed.value, + ); + if (!result.ok) throw result.error; + return reconstructionObligationLedgerSchema.parse( + result.value.normalized_result, + ); +}; + +/** Create provenance for each required case from one source Evidence record. */ +export const originalCases = ( + obligation: ReconstructionObligationLedger["obligations"][number], + evidenceId: string, +) => + obligation.required_case_kinds.map((caseKind) => ({ + kind: caseKind, + evidence_id: evidenceId, + location: `/normalized_result/cases/${caseKind}`, + })); + +/** Create a minimal reviewed obligation for ledger closure scenarios. */ +export const reviewedObligation = ( + obligationId: string, + evidenceId: string, +): ReviewedReconstructionObligation => ({ + obligation_id: obligationId, + obligation_version: 1, + title: `Reviewed obligation ${obligationId}`, + application_layer: "other", + family: "reviewed", + target: { + artifact_sha256: null, + application_node_id: null, + semantic_node_id: null, + location: `/reviewed/${obligationId}`, + }, + required: true, + required_case_kinds: ["positive"], + required_original_authority: "static", + required_fixture_authority: "unit", + required_verifier_authority: "unit", + requires_parser_type: false, + dependency_obligation_ids: [], + residual_unknown_ids: [], + unavailable_authority: [], + required_next_evidence: ["Bind an owner and verifier."], + disposition: "active", + review_evidence_ids: [evidenceId], +}); diff --git a/src/application/Setup.planning.test.ts b/src/application/Setup.planning.test.ts index 3654c2dcf..dbcd96f6f 100644 --- a/src/application/Setup.planning.test.ts +++ b/src/application/Setup.planning.test.ts @@ -165,15 +165,6 @@ describe("setup workflow lifecycle and provider planning", () => { remediation: null, }, ]; - host.productRegistrations = [ - { - client: "codex", - config_path: "/codex.toml", - command: ["rea", "mcp"], - state: "aligned", - remediation: null, - }, - ]; const progress: SetupProgressEvent[] = []; await runSetup( { ...options(true), onProgress: (event) => progress.push(event) }, diff --git a/src/application/Setup.recovery.test.ts b/src/application/Setup.recovery.test.ts index 686a3d903..57765ec23 100644 --- a/src/application/Setup.recovery.test.ts +++ b/src/application/Setup.recovery.test.ts @@ -194,7 +194,7 @@ describe("setup scoped readiness", () => { expect(result.status).toBe("needs_human"); expect(host.hopperInstalls).toBe(0); expect(result.remediation).toBe( - "Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux; configure an existing supported provider instead.", + "Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and CachyOS; configure an existing supported provider instead.", ); }); diff --git a/src/application/SetupHost.ts b/src/application/SetupHost.ts index 6bda5b494..0b1c9910b 100644 --- a/src/application/SetupHost.ts +++ b/src/application/SetupHost.ts @@ -82,7 +82,7 @@ export const hostRemediation = async ( : undefined; } if ((await host.linuxDistribution())?.supported === true) return undefined; - return "Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux; configure an existing supported provider instead."; + return "Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and CachyOS; configure an existing supported provider instead."; }; /** Production setup effects for Hopper, agent configuration, and the canonical skill directory. */ diff --git a/src/application/SetupInstallFailure.test.ts b/src/application/SetupInstallFailure.test.ts deleted file mode 100644 index 088fc4b91..000000000 --- a/src/application/SetupInstallFailure.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - setupInstallFailure, - type HopperInstallFailureReason, -} from "./SetupInstallFailure.js"; - -describe("setup installer failure projection", () => { - it.each([ - ["unsupported_host", "unsupported_host"], - ["download", "download_failed"], - ["release_metadata", "download_failed"], - ["integrity", "integrity_mismatch"], - [ - "authorization_or_package_manager", - "authorization_or_package_manager_failed", - ], - ["launcher_missing", "launcher_missing"], - ["runtime_dependencies", "runtime_dependency_unavailable"], - ["unsupported_hopper_build", "unsupported_hopper_build"], - ["cancelled", "setup_cancelled"], - ["destination_exists", "destination_exists"], - ["mount", "mount_failed"], - ["bundle", "bundle_invalid"], - ["copy", "copy_failed"], - ] as const)("maps %s to %s", (reason, code) => { - const result = setupInstallFailure(reason as HopperInstallFailureReason); - expect(result).toMatchObject({ status: "failed", code }); - expect(result).toMatchObject({ remediation: expect.stringMatching(/\S/u) }); - }); -}); diff --git a/src/application/SetupRegistrationEnvironment.test.ts b/src/application/SetupRegistrationEnvironment.test.ts deleted file mode 100644 index 295794c76..000000000 --- a/src/application/SetupRegistrationEnvironment.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import type { DoctorProviderInspection } from "./Doctor.js"; -import { providerRegistrationEnvironment } from "./SetupRegistrationEnvironment.js"; - -const inspection = ( - overrides: Partial = {}, -): DoctorProviderInspection => ({ - id: "fixture", - configured: true, - available: false, - providerVersion: "1", - registrationEnvironment: { - REA_FIXTURE_PROVIDER_ROOT: "C:\\Tools\\Provider", - }, - checks: [], - ...overrides, -}); - -describe("provider registration settings", () => { - it("retains valid published paths when runtime capabilities are unavailable", () => { - expect(providerRegistrationEnvironment([inspection()])).toEqual({ - REA_FIXTURE_PROVIDER_ROOT: "C:\\Tools\\Provider", - }); - }); - - it("does not invent settings when the adapter cannot publish an installation", () => { - expect( - providerRegistrationEnvironment([ - inspection({ configured: false, registrationEnvironment: {} }), - ]), - ).toEqual({}); - }); - - it("keeps available-provider settings unchanged", () => { - expect( - providerRegistrationEnvironment([inspection({ available: true })]), - ).toEqual({ REA_FIXTURE_PROVIDER_ROOT: "C:\\Tools\\Provider" }); - }); -}); diff --git a/src/application/StoreFileLock.ts b/src/application/StoreFileLock.ts deleted file mode 100644 index 4f79cc060..000000000 --- a/src/application/StoreFileLock.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { - lstat, - mkdtemp, - open, - readdir, - rename, - rm, - rmdir, - unlink, -} from "node:fs/promises"; -import { dirname, join } from "node:path"; - -import { readBoundedFileBytes } from "../process/BoundedFileBytes.js"; - -/** One acquired store transaction lock with cleanup bound to its unique owner. */ -export interface StoreFileLock { - readonly release: () => Promise; -} - -class StoreFileLockOccupied extends Error { - readonly code = "EEXIST"; - - constructor(cause?: unknown) { - super("Store transaction lock is occupied", { cause }); - } -} - -/** Atomically publish a populated directory so another owner cannot replace a live lock. */ -export const createStoreFileLock = async ( - path: string, -): Promise => { - if (await exists(path)) throw new StoreFileLockOccupied(); - const staged = await mkdtemp(join(dirname(path), ".rea-store-lock-")); - const owner = `owner-${randomUUID()}`; - try { - const handle = await open(join(staged, owner), "wx", 0o600); - try { - await handle.writeFile(`${String(process.pid)}\n`, "utf8"); - await handle.sync(); - } finally { - await handle.close(); - } - try { - // Rename cannot replace a nonempty directory: the owner entry is present before publication. - await rename(staged, path); - } catch (cause: unknown) { - if (await exists(path)) throw new StoreFileLockOccupied(cause); - throw cause; - } - return { - release: async () => { - await removeOwner(path, owner).catch((cause: unknown) => { - // best-effort cleanup: lock release must not reject unhandled. - void cause; - }); - }, - }; - } finally { - await rm(staged, { recursive: true, force: true }); - } -}; - -/** Recover a dead owner's directory lock or a legacy PID file; leave live and unknown owners intact. */ -export const removeStaleStoreFileLock = async ( - path: string, -): Promise => { - try { - const stats = await lstat(path); - if (stats.isFile()) { - if (!(await ownerIsDead(path))) return false; - await unlink(path); - return true; - } - if (!stats.isDirectory()) return false; - const entries = await readdir(path); - if (entries.length === 0) { - await rmdir(path); - return true; - } - const owner = entries[0]; - if ( - entries.length !== 1 || - owner === undefined || - !/^owner-[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/u.test(owner) || - !(await ownerIsDead(join(path, owner))) - ) - return false; - await removeOwner(path, owner); - return true; - } catch (cause: unknown) { - // best-effort cleanup: stale lock probing; failure means the lock stands. - void cause; - return false; - } -}; - -const removeOwner = async (path: string, owner: string): Promise => { - // A successor has a different entry, so stale cleanup cannot make its directory empty. - await unlink(join(path, owner)); - await rmdir(path); -}; - -const ownerIsDead = async (path: string): Promise => { - const stats = await lstat(path); - if (!stats.isFile() || stats.size > 32) return false; - const handle = await open(path, "r"); - let bytes: Buffer | undefined; - try { - bytes = await readBoundedFileBytes(handle, 32); - } finally { - await handle.close(); - } - const encoded = bytes?.toString("utf8").trim(); - return ( - encoded !== undefined && - /^[1-9][0-9]{0,9}$/u.test(encoded) && - !processIsAlive(Number(encoded)) - ); -}; - -const exists = async (path: string): Promise => { - try { - await lstat(path); - return true; - } catch (cause: unknown) { - if (errorCode(cause) === "ENOENT") return false; - throw cause; - } -}; - -const processIsAlive = (pid: number): boolean => { - try { - process.kill(pid, 0); - return true; - } catch (cause: unknown) { - return errorCode(cause) !== "ESRCH"; - } -}; - -const errorCode = (cause: unknown): unknown => - typeof cause === "object" && cause !== null && "code" in cause - ? cause.code - : undefined; diff --git a/src/application/WebNetworkCapturePort.ts b/src/application/WebNetworkCapturePort.ts new file mode 100644 index 000000000..5f9cd5545 --- /dev/null +++ b/src/application/WebNetworkCapturePort.ts @@ -0,0 +1,15 @@ +import type { ExecutionOptions } from "./AnalysisProvider.js"; +import type { AnalysisError } from "../domain/analysisErrorBase.js"; +import type { Result } from "../domain/result.js"; +import type { + InspectWebNetworkCaptureInput, + WebNetworkCapture, +} from "../domain/webNetworkCapture.js"; + +/** Replaceable offline capture port; producer protocols remain in format adapters. */ +export interface WebNetworkCapturePort { + inspect( + input: InspectWebNetworkCaptureInput, + options?: ExecutionOptions, + ): Promise>; +} diff --git a/src/application/WebNetworkCaptureService.test.ts b/src/application/WebNetworkCaptureService.test.ts new file mode 100644 index 000000000..0105e2d3f --- /dev/null +++ b/src/application/WebNetworkCaptureService.test.ts @@ -0,0 +1,340 @@ +import { expect, it } from "vitest"; +import { WebNetworkCaptureService } from "./WebNetworkCaptureService.js"; +import { historicalHar } from "../../tests/fixtures/historicalHar.js"; +import { ok } from "../domain/result.js"; +import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; +import { parseEvidence } from "../domain/evidence.js"; +import { + webNetworkCaptureSchema, + type WebNetworkCapture, +} from "../domain/webNetworkCapture.js"; + +it.each(["REDACTED", "[", "]", "/capture.har"])( + "excludes sensitive path text even when a replacement marker would collide: %s", + async (literal) => { + const report = fixture(); + report.artifact.path = `/capture.har/${literal}`; + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(report)), + }).inspect({ + capture_path: report.artifact.path, + format: "har", + sensitive_values: [literal], + }); + if (!result.ok) throw result.error; + expect(result.value.subject?.local_path).not.toContain(literal); + expect(result.value.parameters.capture_path).not.toContain(literal); + expect( + result.value.locations.every( + (location) => + location.kind !== "artifact-path" || !location.path.includes(literal), + ), + ).toBe(true); + expect(result.value.subject?.digest.sha256).toBe(report.artifact.sha256); + }, +); + +it("keeps observed artifact identity when the entire sensitive path is excluded", async () => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: ["/", "[", "REDACTED"], + }); + if (!result.ok) throw result.error; + expect(result.value.subject).toMatchObject({ + local_path: "", + digest: { sha256: "a".repeat(64) }, + }); + expect(result.value.locations).toEqual([]); +}); + +const fixture = (): WebNetworkCapture => ({ + decoder: { id: "test-historical-port", name: "test port", version: "1" }, + container: { + reported: null, + numeric_literals: [], + redactions: [], + records_pointer: "/log/entries", + }, + total_records: 1, + records: [ + { + ordinal: 0, + location: { kind: "json-pointer", pointer: "/log/entries/0" }, + reported: historicalHar().log.entries[0] ?? null, + numeric_literals: [], + binary_fields: [], + redactions: [], + limitations: [], + }, + ], + artifact: { path: "/capture.har", sha256: "a".repeat(64), bytes: 10 }, + format: "har", + runtime_attribution: "unknown", + limitations: ["historical fixture"], +}); + +it.each(["a".repeat(64), "aaaa"])( + "marks an explicitly sensitive artifact digest unknown without inventing another identity: %s", + async (literal) => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: [literal], + }); + if (!result.ok) throw result.error; + expect(result.value.subject).toBeNull(); + expect(result.value.normalized_result).toMatchObject({ + artifact: { path: "/capture.har", sha256: null, bytes: 10 }, + }); + expect(result.value.parameters.capture_path).toBe("/capture.har"); + expect(result.value.locations).toEqual([ + { kind: "artifact-path", path: "/capture.har" }, + ]); + expect(parseEvidence(result.value)).toEqual(result.value); + }, +); + +it("projects the unknown-subject explanation before generating its Evidence ID", async () => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: ["a"], + }); + if (!result.ok) throw result.error; + expect(result.value.subject).toBeNull(); + expect(result.value.limitations.every((text) => !text.includes("a"))).toBe( + true, + ); + expect(parseEvidence(result.value)).toEqual(result.value); +}); + +it("rejects an adapter omitting its original observed digest before caller projection", async () => { + const report = fixture(); + report.artifact.sha256 = null; + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(report)), + }).inspect({ capture_path: report.artifact.path, format: "har" }); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error._tag).toBe("AnalysisOutputError"); +}); + +it.each([ + ["producer"], + ["artifact"], + ["coordinates"], + ["producer", "REDACTED", "…"], + ["producer", "[", "]"], +])( + "excludes declared literals from all authored limitations: %j", + async (...literals) => { + const report = fixture(); + report.artifact.path = "/artifact/capture.har"; + report.limitations = [ + "Historical producer artifact coordinates are retained.", + ]; + const first = report.records[0]; + if (first === undefined) throw new Error("fixture missing record"); + first.limitations = ["This is producer-decoded artifact evidence."]; + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(report)), + }).inspect({ + capture_path: report.artifact.path, + format: "har", + sensitive_values: literals, + }); + if (!result.ok) throw result.error; + const projected = webNetworkCaptureSchema.parse( + result.value.normalized_result, + ); + const texts = [ + result.value.limitations, + projected.limitations, + ...projected.records.map((record) => record.limitations), + ].flat(); + for (const literal of literals) + expect(texts.every((text) => !text.includes(literal))).toBe(true); + expect(result.value.subject?.digest.sha256).toBe(report.artifact.sha256); + expect(projected.decoder).toEqual(report.decoder); + expect(projected.records[0]?.reported).toEqual(first.reported); + }, +); + +it("keeps explicitly sensitive values out of Evidence parameters and preserves historical authority", async () => { + const service = new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }); + const result = await service.inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: ["explicit-private-value"], + }); + if (!result.ok) throw result.error; + expect(result.value.parameters).toMatchObject({ + sensitive_value_count: 1, + record_ordinals: [0], + }); + expect(JSON.stringify(result.value)).not.toContain("explicit-private-value"); + expect(result.value.authority).toBe("historical-reference"); + expect(result.value.provider).toEqual(fixture().decoder); +}); + +it.each(["har", "ha"])( + "omits a format parameter containing a declared sensitive literal: %s", + async (literal) => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: [literal], + }); + if (!result.ok) throw result.error; + expect(result.value.parameters).not.toHaveProperty("format"); + expect(JSON.stringify(result.value.parameters)).not.toContain(literal); + expect(result.value.normalized_result).toMatchObject({ format: "har" }); + }, +); + +it.each([ + { record_ordinals: [0, 0] }, + { record_ordinals: [1] }, + { capture_path: "relative.har" }, +])("rejects invalid caller selection %j", async (extra) => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ capture_path: "/capture.har", format: "har", ...extra }); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error._tag).toBe("AnalysisInputError"); +}); + +it("rejects a port changing the artifact identity or producer ordinal sequence", async () => { + const report = fixture(); + report.artifact.path = "/different.har"; + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(report)), + }).inspect({ capture_path: "/capture.har", format: "har" }); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error._tag).toBe("AnalysisOutputError"); +}); + +it("applies an explicit sensitive path literal after verifying the selected artifact", async () => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: ["capture.har"], + }); + if (!result.ok) throw result.error; + expect(JSON.stringify(result.value)).not.toContain("capture.har"); + expect(result.value.parameters.capture_path).toBe(""); +}); + +it.each(["log", "entries"])( + "excludes a sensitive producer coordinate without inventing a replacement: %s", + async (literal) => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: [literal], + }); + if (!result.ok) throw result.error; + expect(result.value.normalized_result).toMatchObject({ + container: { records_pointer: null }, + records: [ + { + ordinal: 0, + location: { kind: "unknown", reason: "explicit-sensitive-value" }, + }, + ], + }); + expect(JSON.stringify(result.value)).not.toContain(literal); + }, +); + +it("excludes an escaped sensitive sidecar coordinate while preserving its reported field", async () => { + const report = fixture(); + report.container.reported = { "field/name": 42 }; + report.container.numeric_literals = [ + { pointer: "/field~1name", producer_type: "json-number", literal: "42" }, + ]; + report.container.redactions = [ + { pointer: "/field~1name", reason: "explicit-sensitive-value" }, + ]; + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(report)), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: ["~1"], + }); + if (!result.ok) throw result.error; + expect(result.value.normalized_result).toMatchObject({ + container: { + reported: { "field/name": 42 }, + numeric_literals: [], + redactions: [], + }, + }); + expect(JSON.stringify(result.value)).not.toContain("~1"); +}); + +it.each([ + { literal: "12345", input: { record_ordinals: [12345] } }, + { literal: "Each", input: { record_ordinals: [0, 0] } }, + { literal: "absolute", input: { capture_path: "relative.har" } }, + { literal: "secret", input: { mysecret: true } }, +])( + "excludes declared text from application-generated input errors: $literal", + async ({ literal, input }) => { + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(fixture())), + }).inspect({ + capture_path: "/capture.har", + format: "har", + sensitive_values: [literal], + ...input, + }); + if (result.ok) throw new Error("Expected invalid selection"); + expect(result.error._tag).toBe("AnalysisInputError"); + expect(JSON.stringify(projectAnalysisError(result.error))).not.toContain( + literal, + ); + }, +); + +it.each(["12345", "67890", "234", "789"])( + "excludes sensitive native byte-range coordinates: %s", + async (literal) => { + const report = fixture(); + report.format = "mitmproxy"; + report.artifact.path = "/capture.mitm"; + const record = report.records[0]; + if (record === undefined) throw new Error("record missing"); + record.location = { kind: "byte-range", offset: 12345, bytes: 67890 }; + const result = await new WebNetworkCaptureService({ + inspect: () => Promise.resolve(ok(report)), + }).inspect({ + capture_path: report.artifact.path, + format: "mitmproxy", + sensitive_values: [literal], + }); + if (!result.ok) throw result.error; + expect(result.value.normalized_result).toMatchObject({ + records: [ + { location: { kind: "unknown", reason: "explicit-sensitive-value" } }, + ], + }); + expect(result.value.raw_result).toBeNull(); + expect(parseEvidence(result.value)).toEqual(result.value); + }, +); diff --git a/src/application/WebNetworkCaptureService.ts b/src/application/WebNetworkCaptureService.ts new file mode 100644 index 000000000..ca86b7288 --- /dev/null +++ b/src/application/WebNetworkCaptureService.ts @@ -0,0 +1,202 @@ +import { redactExplicitFailure } from "../domain/explicitSensitiveFailure.js"; +import { excludeCaptureCoordinates } from "../domain/webNetworkCaptureCoordinates.js"; +import { redactExplicitText } from "../domain/explicitSensitiveValues.js"; +import { isAbsolute } from "node:path"; +import type { ExecutionOptions } from "./AnalysisProvider.js"; +import type { WebNetworkCapturePort } from "./WebNetworkCapturePort.js"; +import type { AnalysisError } from "../domain/analysisErrorBase.js"; +import { + AnalysisCancelledError, + AnalysisInputError, + AnalysisOutputError, +} from "../domain/analysisErrorCore.js"; +import { createEvidence, type Evidence } from "../domain/evidence.js"; +import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { jsonObjectSchema } from "../domain/jsonValue.js"; +import { err, ok, type Result } from "../domain/result.js"; +import { + inspectWebNetworkCaptureInputSchema, + webNetworkCaptureSchema, +} from "../domain/webNetworkCapture.js"; + +const OPERATION = "inspect_web_network_capture"; + +/** Shared CLI/MCP historical inspection preserves caller selection and producer record identity. */ +export class WebNetworkCaptureService { + constructor(readonly port: WebNetworkCapturePort) {} + + /** Return selected complete records inline, without inventing live browser attribution. */ + async inspect( + rawInput: unknown, + options?: ExecutionOptions, + ): Promise> { + if (options?.signal?.aborted) + return err(new AnalysisCancelledError(OPERATION)); + const declarations = + inspectWebNetworkCaptureInputSchema.shape.sensitive_values.safeParse( + typeof rawInput === "object" && + rawInput !== null && + "sensitive_values" in rawInput + ? rawInput.sensitive_values + : undefined, + ); + const failure = (error: AnalysisError): Result => + err( + redactExplicitFailure( + error, + declarations.success ? declarations.data : [], + ), + ); + const parsed = inspectWebNetworkCaptureInputSchema.safeParse(rawInput); + if (!parsed.success) + return failure( + new AnalysisInputError( + OPERATION, + { cause: parsed.error }, + projectInputIssues(parsed.error.issues, rawInput), + ), + ); + const input = parsed.data; + if (!isAbsolute(input.capture_path)) + return failure( + new AnalysisInputError(OPERATION, undefined, [ + { + path: ["capture_path"], + reason: "invalid_format", + message: "Expected an absolute filesystem path on this host.", + }, + ]), + ); + if ( + input.record_ordinals !== undefined && + new Set(input.record_ordinals).size !== input.record_ordinals.length + ) + return failure( + new AnalysisInputError(OPERATION, undefined, [ + { + path: ["record_ordinals"], + reason: "invalid_format", + message: "Each requested producer ordinal must occur once.", + }, + ]), + ); + const loaded = await this.port.inspect(input, options); + if (!loaded.ok) return failure(loaded.error); + if (options?.signal?.aborted) + return failure(new AnalysisCancelledError(OPERATION)); + const report = webNetworkCaptureSchema.safeParse(loaded.value); + if (!report.success) + return failure( + new AnalysisOutputError( + OPERATION, + "Capture adapter returned malformed historical evidence.", + ), + ); + const value = report.data; + const artifactSha256 = value.artifact.sha256; + if (artifactSha256 === null) + return failure( + new AnalysisOutputError( + OPERATION, + "Capture adapter omitted its observed artifact digest before explicit projection.", + ), + ); + if ( + value.artifact.path !== input.capture_path || + value.format !== input.format || + value.total_records !== value.records.length || + value.records.some((record, index) => record.ordinal !== index) + ) + return failure( + new AnalysisOutputError( + OPERATION, + "Capture adapter changed the selected artifact, format or original record sequence.", + ), + ); + const safeValue = excludeCaptureCoordinates(value, input.sensitive_values); + const redactText = (text: string): string => + redactExplicitText(text, input.sensitive_values); + const selected = + input.record_ordinals ?? value.records.map(({ ordinal }) => ordinal); + const records = []; + for (const ordinal of selected) { + const record = safeValue.records[ordinal]; + if (record === undefined) + return failure( + new AnalysisInputError(OPERATION, undefined, [ + { + path: ["record_ordinals"], + reason: "invalid_format", + message: `Producer ordinal ${ordinal} is outside the retained capture (${value.total_records} records).`, + }, + ]), + ); + records.push({ + ...record, + limitations: record.limitations.map(redactText), + }); + } + const safePath = input.sensitive_values.some((literal) => + input.capture_path.includes(literal), + ) + ? "" + : input.capture_path; + const safeSha256 = input.sensitive_values.some((literal) => + artifactSha256.includes(literal), + ) + ? null + : artifactSha256; + const subjectUnavailableReason = redactText( + "The explicitly sensitive artifact digest is excluded; its cryptographic identity is unknown in this Evidence.", + ); + const limitations = [ + ...safeValue.limitations, + ...(safePath === "" + ? [ + "The explicitly sensitive artifact path is excluded; its size remains available.", + ] + : []), + ].map(redactText); + const parameters = jsonObjectSchema.parse({ + capture_path: safePath, + ...(input.sensitive_values.some((literal) => + input.format.includes(literal), + ) + ? {} + : { format: input.format }), + record_ordinals: selected, + sensitive_value_count: input.sensitive_values.length, + }); + return ok( + createEvidence( + safeSha256 === null + ? undefined + : { + path: safePath, + sha256: safeSha256, + format: "file", + }, + value.decoder, + { + operation: OPERATION, + parameters, + result: { + ...safeValue, + artifact: { ...value.artifact, path: safePath, sha256: safeSha256 }, + records, + limitations: + safeSha256 === null + ? [...limitations, subjectUnavailableReason] + : limitations, + }, + confidence: "observed", + authority: "historical-reference", + limitations, + ...(safeSha256 === null ? { subjectUnavailableReason } : {}), + locations: + safePath === "" ? [] : [{ kind: "artifact-path", path: safePath }], + }, + ), + ); + } +} diff --git a/src/application/WindowsCapabilities.ts b/src/application/WindowsCapabilities.ts index 3e95bed1d..dfd092c69 100644 --- a/src/application/WindowsCapabilities.ts +++ b/src/application/WindowsCapabilities.ts @@ -10,7 +10,7 @@ import { import { probeProcessCaptureCapability, type ProcessCaptureCapability, -} from "./ProcessCaptureCapability.js"; +} from "../process/capture/ProcessCaptureCapability.js"; /** One named Windows host observation without inferred authority. */ export type WindowsCapabilityOutcome = diff --git a/src/application/ArtifactExtractionDestination.ts b/src/application/artifacts/ArtifactExtractionDestination.ts similarity index 100% rename from src/application/ArtifactExtractionDestination.ts rename to src/application/artifacts/ArtifactExtractionDestination.ts diff --git a/src/application/binary/AnalysisProviderRegistry.test.ts b/src/application/binary/AnalysisProviderRegistry.test.ts index acbbae5fc..8c0b2ed4e 100644 --- a/src/application/binary/AnalysisProviderRegistry.test.ts +++ b/src/application/binary/AnalysisProviderRegistry.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it } from "vitest"; -import { - AnalysisProviderRegistry, - type AnalysisProviderBinding, -} from "./AnalysisProviderRegistry.js"; +import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js"; import { createAnalysisExecution, type AnalysisClient, @@ -33,132 +30,77 @@ const ARTIFACT_TARGET: BinaryTarget = { format: "asar", }; -describe("analysis provider registry: discovery and selection", () => { - it("admits overlapping operations, sorts discovery, and rejects duplicate IDs", () => { - const alpha = candidate("alpha"); - const beta = candidate("beta"); - const registry = new AnalysisProviderRegistry([ - beta.provider, - alpha.provider, - ]); +it("rejects malformed provider capability declarations", () => { + const alpha = candidate("alpha"); + expect( + () => new AnalysisProviderRegistry([alpha.provider, alpha.provider]), + ).toThrow(/Duplicate analysis provider ID: alpha/u); + expect( + () => + new AnalysisProviderRegistry([ + { + ...alpha.provider, + capabilities: () => { + const descriptor = capability(alpha.provider.identity()); + return [descriptor, descriptor]; + }, + }, + ]), + ).toThrow(/declares operation address_name more than once/u); + expect( + () => + new AnalysisProviderRegistry([ + { + ...alpha.provider, + capabilities: () => [ + capability({ ...alpha.provider.identity(), name: "forged" }), + ], + }, + ]), + ).toThrow(/published mismatched capability provenance/u); + expect( + () => new AnalysisProviderRegistry([candidate("auto").provider]), + ).toThrow(/Invalid analysis provider ID: auto/u); +}); - expect(registry.identities().map(({ id }) => id)).toEqual([ - "alpha", - "beta", - ]); - expect( - registry.candidates().map(({ provider, targetSupport }) => ({ - id: provider.id, - support: targetSupport.status, - })), - ).toEqual([ - { id: "alpha", support: "unknown" }, - { id: "beta", support: "unknown" }, - ]); - expect(alpha.created).toEqual([]); - expect(beta.created).toEqual([]); - expect( - () => new AnalysisProviderRegistry([alpha.provider, alpha.provider]), - ).toThrow(/Duplicate analysis provider ID: alpha/u); - expect( - () => - new AnalysisProviderRegistry([ - { - ...alpha.provider, - capabilities: () => { - const descriptor = capability(alpha.provider.identity()); - return [descriptor, descriptor]; - }, - }, - ]), - ).toThrow(/declares operation address_name more than once/u); - expect( - () => - new AnalysisProviderRegistry([ - { - ...alpha.provider, - capabilities: () => [ - capability({ - ...alpha.provider.identity(), - name: "mismatched provider", - }), - ], - }, - ]), - ).toThrow(/published mismatched capability provenance/u); - expect( - () => new AnalysisProviderRegistry([candidate("auto").provider]), - ).toThrow(/Invalid analysis provider ID: auto/u); +it("selects the sole available provider and honors explicit selection precedence", async () => { + const alpha = candidate("alpha"); + const beta = candidate("beta", { available: false }); + const sole = await new AnalysisProviderRegistry([ + beta.provider, + alpha.provider, + ]).select(DATABASE_TARGET); + expect(sole).toMatchObject({ + ok: true, + value: { + binding: { + identity: { id: "alpha" }, + selectionSource: "auto-single-candidate", + }, + }, }); - it("selects the sole usable candidate without creating a client", async () => { - const alpha = candidate("alpha"); - const beta = candidate("beta", { available: false }); - const registry = new AnalysisProviderRegistry([ - beta.provider, - alpha.provider, - ]); - - const selected = await registry.select(DATABASE_TARGET); - expect(selected.ok).toBe(true); - if (!selected.ok) throw new Error("provider selection failed"); - if (selected.value.binding === null) - throw new Error("expected a bound provider"); - expect(bindingProjection(selected.value.binding)).toEqual({ - id: "alpha", - source: "auto-single-candidate", - version: "1", - }); - expect(selected.value.candidates).toMatchObject([ - { provider: { id: "alpha", version: "1" }, selected: true }, - { - provider: { id: "beta" }, - selected: false, - availability: { status: "unavailable", code: "runtime_missing" }, + const selected = new AnalysisProviderRegistry( + [alpha.provider, candidate("beta").provider], + "beta", + ); + expect(await selected.select(DATABASE_TARGET)).toMatchObject({ + ok: true, + value: { + binding: { + identity: { id: "beta" }, + selectionSource: "environment", }, - ]); - expect(alpha.created).toEqual([]); - expect(beta.created).toEqual([]); + }, }); - - it("makes auto ambiguity explicit and honors request over environment", async () => { - const alpha = candidate("alpha"); - const beta = candidate("beta"); - const registry = new AnalysisProviderRegistry( - [alpha.provider, beta.provider], - "beta", - ); - - const environment = await registry.select(DATABASE_TARGET); - expect(environment.ok).toBe(true); - if (environment.ok) - expect(environment.value.binding?.selectionSource).toBe("environment"); - expect(environment.ok && environment.value.binding?.identity.id).toBe( - "beta", - ); - - const request = await registry.select(DATABASE_TARGET, "alpha"); - expect(request.ok && request.value.binding?.identity.id).toBe("alpha"); - if (request.ok) - expect(request.value.binding?.selectionSource).toBe("request"); - - const automatic = await registry.select(DATABASE_TARGET, "auto"); - expect(automatic.ok).toBe(false); - if (automatic.ok) return; - expect(automatic.error).toMatchObject({ - reason: "ambiguous", - requestedProviderId: "auto", - candidateIds: ["alpha", "beta"], - }); - expect(projectAnalysisError(automatic.error)).toMatchObject({ - code: "capability_unavailable", - details: { - selection_reason: "ambiguous", - candidate_ids: ["alpha", "beta"], + expect(await selected.select(DATABASE_TARGET, "alpha")).toMatchObject({ + ok: true, + value: { + binding: { + identity: { id: "alpha" }, + selectionSource: "request", }, - }); - expect(alpha.created).toEqual([]); - expect(beta.created).toEqual([]); + }, }); }); @@ -414,9 +356,3 @@ const capability = (provider: ProviderIdentity): CapabilityDescriptor => ({ }, limitations: [], }); - -const bindingProjection = (binding: AnalysisProviderBinding) => ({ - id: binding.identity.id, - source: binding.selectionSource, - version: binding.identity.version, -}); diff --git a/src/application/binary/AnalysisSnapshotCache.test.ts b/src/application/binary/AnalysisSnapshotCache.test.ts index 79ccfaa73..5a7472ba4 100644 --- a/src/application/binary/AnalysisSnapshotCache.test.ts +++ b/src/application/binary/AnalysisSnapshotCache.test.ts @@ -10,15 +10,137 @@ import { createEvidence } from "../../domain/evidence.js"; import { createEvidenceBundle } from "../../domain/evidenceBundle.js"; import { ok } from "../../domain/result.js"; import { createAnalysisExecution } from "../AnalysisProvider.js"; +import { + createAnalysisSnapshotWorkflowEntry, + parseAnalysisSnapshot, + snapshotBinding, + snapshotTarget, +} from "../../domain/analysisSnapshot.js"; +import { + REA_WORKFLOW_PROVIDER, + workflowAnalysisProfile, +} from "../InvestigationProviders.js"; import type { AnalysisOperation, CapabilityDescriptor, } from "../AnalysisProvider.js"; import { AnalysisSnapshotCache, + SNAPSHOT_CACHE_ENTRY_CEILING, isSnapshotCacheable, } from "./AnalysisSnapshotCache.js"; +describe("analysis snapshot cache capacity", () => { + it("shares capacity across query kinds while allowing replacements and fresh partitions", () => { + const cache = new AnalysisSnapshotCache(); + const target = ANALYSIS_SNAPSHOT_TARGET; + const profile = ANALYSIS_SNAPSHOT_PROFILE; + const execution = { + ...createAnalysisExecution("workflow", REA_WORKFLOW_PROVIDER, { + subject: target, + }), + analysisProfile: workflowAnalysisProfile(profile), + }; + const workflowInput = { + target, + profile, + operation: "trace_feature", + parameters: { query: "existing" }, + execution, + }; + for (let index = 0; index < SNAPSHOT_CACHE_ENTRY_CEILING - 1; index += 1) + cache.recordWorkflow({ + ...workflowInput, + parameters: { query: String(index) }, + }); + // Staging and recording both spend the same budget; an existing query can + // still be refreshed when no space remains for a new binding. + const directInput = { + target, + profile, + operation: "address_name" as const, + parameters: { address: "0x1000", document: "fixture" }, + execution: createAnalysisExecution( + "initial", + ANALYSIS_SNAPSHOT_PROVIDER, + { subject: target, analysisProfile: profile }, + ), + }; + cache.record(directInput); + cache.record({ + ...directInput, + parameters: { address: "0x2000", document: "fixture" }, + }); + cache.recordWorkflow(workflowInput); + expect(cache.entries()).toHaveLength(1); + expect(cache.workflowEntries()).toHaveLength( + SNAPSHOT_CACHE_ENTRY_CEILING - 1, + ); + + cache.record({ + ...directInput, + execution: { ...directInput.execution, result: "updated" }, + }); + cache.recordWorkflow({ + ...workflowInput, + parameters: { query: "0" }, + execution: { ...execution, result: "updated" }, + }); + expect( + cache.lookup(target, profile, "address_name", directInput.parameters) + ?.result, + ).toBe("updated"); + expect( + cache.workflowEntries().find(({ parameters }) => parameters.query === "0") + ?.execution.result, + ).toBe("updated"); + + const incoming = createAnalysisSnapshotWorkflowEntry({ + ...workflowInput, + target: snapshotTarget(target), + binding: snapshotBinding(profile), + }); + const evidence = createEvidence(target, REA_WORKFLOW_PROVIDER, { + operation: workflowInput.operation, + parameters: workflowInput.parameters, + result: execution.result, + rawResult: execution.rawResult, + analysisProfile: execution.analysisProfile, + limitations: execution.limitations, + locations: execution.locations, + }); + cache.stage( + parseAnalysisSnapshot({ + target: snapshotTarget(target), + binding: snapshotBinding(profile), + entries: [], + workflow_entries: [incoming], + evidence_bundle: createEvidenceBundle([evidence]), + }), + ); + expect(cache.workflowEntries()).toHaveLength( + SNAPSHOT_CACHE_ENTRY_CEILING - 1, + ); + + const otherTarget = { ...target, sha256: "b".repeat(64) }; + cache.recordWorkflow({ + ...workflowInput, + target: otherTarget, + execution: { ...execution, subject: otherTarget }, + }); + expect(cache.entries()).toEqual([]); + expect(cache.workflowEntries()).toHaveLength(1); + cache.clear(); + cache.record({ + ...directInput, + target: otherTarget, + execution: { ...directInput.execution, subject: otherTarget }, + }); + expect(cache.entries()).toHaveLength(1); + expect(cache.workflowEntries()).toEqual([]); + }); +}); + describe("analysis snapshot cache partitioning", () => { it.each<{ operation: Exclude; diff --git a/src/application/binary/AnalysisSnapshotCache.ts b/src/application/binary/AnalysisSnapshotCache.ts index 473434542..edb39da10 100644 --- a/src/application/binary/AnalysisSnapshotCache.ts +++ b/src/application/binary/AnalysisSnapshotCache.ts @@ -9,6 +9,7 @@ import { err, ok, type Result } from "../../domain/result.js"; import { analysisQueryId, createAnalysisSnapshotEntry, + createAnalysisSnapshotWorkflowEntry, snapshotBinding, snapshotMatchesProfile, snapshotMatchesTarget, @@ -16,6 +17,7 @@ import { parseAnalysisSnapshot, type AnalysisSnapshot, type AnalysisSnapshotEntry, + type AnalysisSnapshotWorkflowEntry, type AnalysisSnapshotBinding, type AnalysisSnapshotTarget, } from "../../domain/analysisSnapshot.js"; @@ -54,6 +56,9 @@ const DOCUMENT_SCOPED_OPERATIONS: ReadonlySet = new Set( ), ); +/** Maximum duplicated result bindings retained across direct and workflow queries. */ +export const SNAPSHOT_CACHE_ENTRY_CEILING = 10_000; + /** Whether an operation is immutable and independent of provider UI state. */ export const isSnapshotCacheable = ( operation: AnalysisOperation, @@ -73,6 +78,7 @@ export const isSnapshotCacheable = ( /** Bounded in-memory cache for one immutable binary identity. */ export class AnalysisSnapshotCache { readonly #entries = new Map(); + readonly #workflowEntries = new Map(); #target: AnalysisSnapshotTarget | undefined; #binding: AnalysisSnapshotBinding | undefined; @@ -93,7 +99,7 @@ export class AnalysisSnapshotCache { /** Replace cache state when the target or selected profile changes. */ select(target: BinaryTarget, profile: AnalysisProfileCommitment): void { - if (!this.matches(target, profile)) this.#entries.clear(); + if (!this.matches(target, profile)) this.#clearEntries(); this.#target = snapshotTarget(target); this.#binding = snapshotBinding(profile); } @@ -109,14 +115,20 @@ export class AnalysisSnapshotCache { snapshot.binding.analysis_profile, )) ) - this.#entries.clear(); + this.#clearEntries(); this.#target = structuredClone(snapshot.target); this.#binding = structuredClone(snapshot.binding); let imported = 0; for (const entry of snapshot.entries) { + if (!this.#entries.has(entry.query_id) && !this.#hasCapacity()) continue; if (!this.#entries.has(entry.query_id)) imported += 1; this.#entries.set(entry.query_id, structuredClone(entry)); } + for (const entry of snapshot.workflow_entries ?? []) { + if (!this.#workflowEntries.has(entry.query_id) && !this.#hasCapacity()) + continue; + this.#workflowEntries.set(entry.query_id, structuredClone(entry)); + } return imported; } @@ -137,6 +149,9 @@ export class AnalysisSnapshotCache { target: snapshotTargetIdentity, binding, entries: this.entries(), + ...(this.workflowEntries().length === 0 + ? {} + : { workflow_entries: this.workflowEntries() }), evidence_bundle: retainedEvidence, }), ); @@ -193,6 +208,47 @@ export class AnalysisSnapshotCache { .map((entry) => structuredClone(entry)); } + /** Return canonical composed-workflow entries for persistence. */ + workflowEntries(): AnalysisSnapshotWorkflowEntry[] { + return [...this.#workflowEntries.values()] + .sort((left, right) => left.query_id.localeCompare(right.query_id)) + .map((entry) => structuredClone(entry)); + } + + /** Record one exact derived workflow result unless the shared cache is full. */ + recordWorkflow(input: { + readonly target: BinaryTarget; + readonly profile: AnalysisProfileCommitment; + readonly operation: string; + readonly parameters: Readonly>; + readonly execution: { + readonly result: JsonValue; + readonly rawResult: JsonValue | null; + readonly provider: AnalysisExecution["provider"]; + readonly analysisProfile: AnalysisProfileCommitment; + readonly limitations: readonly string[]; + readonly locations: AnalysisExecution["locations"]; + readonly subject: AnalysisExecution["subject"]; + }; + }): void { + this.select(input.target, input.profile); + const queryId = analysisQueryId( + snapshotTarget(input.target), + snapshotBinding(input.profile), + input.operation, + input.parameters, + ); + if (!this.#workflowEntries.has(queryId) && !this.#hasCapacity()) return; + const entry = createAnalysisSnapshotWorkflowEntry({ + target: snapshotTarget(input.target), + binding: snapshotBinding(input.profile), + operation: input.operation, + parameters: input.parameters, + execution: input.execution, + }); + this.#workflowEntries.set(entry.query_id, entry); + } + /** Replay an exact provider-specific query, marking its cached provenance. */ lookup( target: BinaryTarget, @@ -246,8 +302,14 @@ export class AnalysisSnapshotCache { readonly execution: AnalysisExecution; }): void { const { target, profile, operation, parameters, execution } = input; - if (this.#entries.size >= 10_000) return; this.select(target, profile); + const queryId = analysisQueryId( + snapshotTarget(target), + snapshotBinding(profile), + operation, + parameters, + ); + if (!this.#entries.has(queryId) && !this.#hasCapacity()) return; const entry = createAnalysisSnapshotEntry({ target: snapshotTarget(target), binding: snapshotBinding(profile), @@ -260,8 +322,20 @@ export class AnalysisSnapshotCache { /** Forget all target-bound entries. */ clear(): void { - this.#entries.clear(); + this.#clearEntries(); this.#target = undefined; this.#binding = undefined; } + + #clearEntries(): void { + this.#entries.clear(); + this.#workflowEntries.clear(); + } + + #hasCapacity(): boolean { + return ( + this.#entries.size + this.#workflowEntries.size < + SNAPSHOT_CACHE_ENTRY_CEILING + ); + } } diff --git a/src/application/binary/BinarySession.ts b/src/application/binary/BinarySession.ts index 7acc84357..2e256bb1a 100644 --- a/src/application/binary/BinarySession.ts +++ b/src/application/binary/BinarySession.ts @@ -30,7 +30,10 @@ import { BinarySessionRecords } from "./BinarySessionRecords.js"; import { binarySessionStatus } from "./BinarySessionStatus.js"; import { resolveSessionOpen, + resolveSessionTarget, + validateResolvedSessionTarget, type BinarySessionOpenOptions, + type ResolvedSessionOpen, } from "./BinarySessionOpen.js"; import { bindExecutionTarget, @@ -150,6 +153,27 @@ export class BinarySession return () => this.#availabilityListeners.delete(listener); } + /** Resolve a target and provider profile without creating a provider client. */ + previewTarget( + target: BinaryTarget, + options: BinarySessionOpenOptions = {}, + ): Promise> { + if (isAborted(options.signal)) + return Promise.resolve(err(new AnalysisCancelledError("open_binary"))); + return resolveSessionTarget({ + router: this.#providerRouter, + current: this.#active, + target, + options, + stagedSnapshotMatches: (target, profile) => + this.matchesSnapshot(target, profile), + }).then((resolved) => + isAborted(options.signal) + ? err(new AnalysisCancelledError("open_binary")) + : resolved, + ); + } + /** * Open or switch targets after draining calls against the current target. * Returns the switch failure even if best-effort restoration also fails. @@ -157,18 +181,49 @@ export class BinarySession open( path: string, options: BinarySessionOpenOptions = {}, + ): Promise> { + return this.#open( + () => + resolveSessionOpen({ + router: this.#providerRouter, + current: this.#active, + path, + options, + stagedSnapshotMatches: (target, profile) => + this.matchesSnapshot(target, profile), + }), + options, + ); + } + + /** Open a previewed target without repeating parsing or provider profile discovery. */ + openResolvedTarget( + resolved: Pick, + options: Pick = {}, + ): Promise> { + return this.#open( + () => + Promise.resolve( + validateResolvedSessionTarget({ + ...resolved, + current: this.#active, + options, + stagedSnapshotMatches: (target, profile) => + this.matchesSnapshot(target, profile), + }), + ), + options, + ); + } + + #open( + resolve: () => Promise>, + options: Pick, ): Promise> { return this.#serialize(async () => { if (isAborted(options.signal)) return err(new AnalysisCancelledError("open_binary")); - const resolved = await resolveSessionOpen({ - router: this.#providerRouter, - current: this.#active, - path, - options, - stagedSnapshotMatches: (target, profile) => - this.matchesSnapshot(target, profile), - }); + const resolved = await resolve(); if (!resolved.ok) return resolved; const { target, route, sameTarget } = resolved.value; const { profile, compatibility } = route; diff --git a/src/application/binary/BinarySessionOpen.ts b/src/application/binary/BinarySessionOpen.ts index e0d425ee9..859d8f413 100644 --- a/src/application/binary/BinarySessionOpen.ts +++ b/src/application/binary/BinarySessionOpen.ts @@ -46,11 +46,22 @@ interface ResolveSessionOpenInput { ) => boolean; } +interface ResolveSessionTargetInput { + readonly router: SessionProviderRouter; + readonly current: CurrentOpenBinding | undefined; + readonly target: BinaryTarget; + readonly options: BinarySessionOpenOptions; + readonly stagedSnapshotMatches: ( + target: BinaryTarget, + profile: AnalysisProfileCommitment | null, + ) => boolean; +} + /** Parse a target, resolve its provider route, and validate snapshot binding. */ export const resolveSessionOpen = async ( input: ResolveSessionOpenInput, ): Promise> => { - const { router, current, path, options, stagedSnapshotMatches } = input; + const { path, options } = input; const parsed = await parseBinaryTarget( path, process.cwd(), @@ -59,7 +70,14 @@ export const resolveSessionOpen = async ( options.formatHint, ); if (!parsed.ok) return parsed; - const target = parsed.value; + return resolveSessionTarget({ ...input, target: parsed.value }); +}; + +/** Resolve a known target's provider route and validate snapshot binding. */ +export const resolveSessionTarget = async ( + input: ResolveSessionTargetInput, +): Promise> => { + const { router, current, target, options } = input; const sameTarget = current?.target.path === target.path && snapshotMatchesTarget(snapshotTarget(current.target), target); @@ -68,7 +86,22 @@ export const resolveSessionOpen = async ( ? ({ ok: true, value: current.route } as const) : await router.resolve(target, options.providerId, options.signal); if (!resolvedRoute.ok) return resolvedRoute; - const route = resolvedRoute.value; + return validateResolvedSessionTarget({ + ...input, + route: resolvedRoute.value, + }); +}; + +/** Revalidate a previewed route against the current session and snapshot binding. */ +export const validateResolvedSessionTarget = ( + input: Omit & { + readonly route: SessionProviderRoute; + }, +): Result => { + const { current, target, route, options, stagedSnapshotMatches } = input; + const sameTarget = + current?.target.path === target.path && + snapshotMatchesTarget(snapshotTarget(current.target), target); const snapshotError = validateSnapshot({ snapshot: options.snapshot, target, diff --git a/src/application/binary/BinarySessionRecords.ts b/src/application/binary/BinarySessionRecords.ts index 3ec801bda..ee97d8849 100644 --- a/src/application/binary/BinarySessionRecords.ts +++ b/src/application/binary/BinarySessionRecords.ts @@ -68,7 +68,8 @@ export abstract class BinarySessionRecords { ): Result { const imported = this.#records.mergeEvidenceBundle(bundle); if (!imported.ok) return imported; - if (imported.value.changed) this.#emitSnapshotChanged(); + if (imported.value.metadataChanged) this.invalidateSnapshot(); + else if (imported.value.changed) this.#emitSnapshotChanged(); return ok(imported.value.recordsAdded); } @@ -159,6 +160,43 @@ export abstract class BinarySessionRecords { this.#emitSnapshotChanged(); } + /** Retain one derived workflow result alongside its provider cache entries. */ + recordWorkflowSnapshot(input: { + readonly operation: string; + readonly parameters: Readonly< + Record + >; + readonly execution: Parameters< + AnalysisSnapshotCache["recordWorkflow"] + >[0]["execution"]; + }): Result { + const active = this.activeAnalysisBinding(); + if (active === undefined || active.profile === null) + return err( + new EvidenceIntegrityError( + "Workflow snapshot entries require an active concrete provider profile", + ), + ); + try { + this.#snapshot.recordWorkflow({ + target: active.target, + profile: active.profile, + ...input, + }); + this.#emitSnapshotChanged(); + return ok(null); + } catch (cause: unknown) { + return err( + new EvidenceIntegrityError( + cause instanceof Error + ? cause.message + : "Workflow snapshot entry validation failed", + { cause }, + ), + ); + } + } + protected invalidateSnapshot(): void { this.#snapshot.clear(); this.#snapshotInvalidated = true; diff --git a/src/application/investigation/EvidenceLedger.residualUnknown.test.ts b/src/application/investigation/EvidenceLedger.residualUnknown.test.ts index 1dac1b9a1..a199f5626 100644 --- a/src/application/investigation/EvidenceLedger.residualUnknown.test.ts +++ b/src/application/investigation/EvidenceLedger.residualUnknown.test.ts @@ -43,7 +43,6 @@ const input = ( overrides: Partial = {}, ): RecordUnknownInput => recordUnknownInputSchema.parse({ - approved: true, question, severity: "high", domain: "protocol", @@ -60,7 +59,6 @@ const update = ( overrides: Readonly> = {}, ) => updateUnknownInputSchema.parse({ - approved: true, unknown_id: unknown.unknown_id, expected_revision: unknown.revision, status: "investigating", @@ -77,6 +75,78 @@ const update = ( }); describe("residual unknown registry reads and identity", () => { + it("merges record-only bundles without changing retained unknown history", () => { + const store = ledger(); + const created = store.recordUnknown( + input("Does a later record preserve this unknown?"), + mutation("record-only-import"), + ); + expect(created.ok).toBe(true); + const originalUnknowns = store.export().unknowns; + const addition = evidence("record-only-addition"); + const bundle = createEvidenceBundle([addition]); + + expect(store.import(bundle)).toEqual({ + ok: true, + value: { + recordsAdded: 1, + unknownsAdded: 0, + changed: true, + metadataChanged: false, + }, + }); + expect(store.export().unknowns).toEqual(originalUnknowns); + expect(store.import(bundle)).toEqual({ + ok: true, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: false, + metadataChanged: false, + }, + }); + }); +}); + +describe("residual unknown dependency histories", () => { + it("imports a valid dependency chain deeper than the JavaScript call stack", () => { + const records = []; + const unknowns: ResidualUnknown[] = []; + let previousId: string | undefined; + for (let index = 0; index < 10_000; index += 1) { + const mutationEvidence = mutation(`deep-chain-${index}`); + records.push(mutationEvidence); + const relationships = + previousId === undefined + ? [] + : [{ type: "depends-on" as const, unknown_id: previousId }]; + const unknown = createResidualUnknown( + input(`Does chain item ${index} meet the expected state?`, { + domain: "chain-profile", + required_authority: null, + relationships, + }), + mutationEvidence.evidence_id, + null, + ); + unknowns.push(unknown); + previousId = unknown.unknown_id; + } + const bundle = createEvidenceBundle(records, unknowns); + const store = ledger(); + + expect(store.import(bundle)).toEqual({ + ok: true, + value: { + recordsAdded: 10_000, + unknownsAdded: 10_000, + changed: true, + metadataChanged: false, + }, + }); + expect(store.export()).toEqual(bundle); + }); + it("returns detached unknowns and evidence bundles from every read surface", () => { const store = ledger(); expect(store.record(evidence("detached-record")).ok).toBe(true); diff --git a/src/application/investigation/EvidenceLedger.test.ts b/src/application/investigation/EvidenceLedger.test.ts index 46fe6e68d..2eeb94616 100644 --- a/src/application/investigation/EvidenceLedger.test.ts +++ b/src/application/investigation/EvidenceLedger.test.ts @@ -38,9 +38,55 @@ describe("evidence ledger recording", () => { }); expect(ledger.import(createEvidenceBundle([evidence]))).toEqual({ ok: true, - value: { recordsAdded: 0, unknownsAdded: 0, changed: false }, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: false, + metadataChanged: false, + }, }); expect(ledger.export().records).toEqual([evidence]); + const relocatedBundleRecord = createEvidence( + { ...TARGET, path: "/relocated/fixture-renamed" }, + PROVIDER, + { operation: "health", parameters: {}, result: true }, + ); + expect( + ledger.import(createEvidenceBundle([relocatedBundleRecord])), + ).toEqual({ + ok: true, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: true, + metadataChanged: true, + }, + }); + expect(ledger.get(evidence.evidence_id)).toEqual(relocatedBundleRecord); + expect(ledger.export().records).toEqual([relocatedBundleRecord]); + expect( + ledger.import(createEvidenceBundle([relocatedBundleRecord])), + ).toEqual({ + ok: true, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: false, + metadataChanged: false, + }, + }); + const conflictingBundle = createEvidenceBundle([ + evidence, + relocatedBundleRecord, + ]); + expect(ledger.import(conflictingBundle).ok).toBe(false); + expect(ledger.get(evidence.evidence_id)).toEqual(relocatedBundleRecord); + ledger.clear(); + expect(ledger.export().records).toEqual([]); + }); + + it("ignores JSON object key order during imported identity checks", () => { + const ledger = new EvidenceLedger(); const ordered = createEvidence(TARGET, PROVIDER, { operation: "health", parameters: { alpha: 1, beta: 2 }, @@ -60,11 +106,77 @@ describe("evidence ledger recording", () => { ); expect(ledger.import(reorderedBundle)).toEqual({ ok: true, - value: { recordsAdded: 0, unknownsAdded: 0, changed: false }, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: false, + metadataChanged: false, + }, }); ledger.clear(); expect(ledger.export().records).toEqual([]); }); +}); + +describe("evidence ledger metadata imports", () => { + it("reports and commits metadata replacement when importing unknown history", () => { + const ledger = new EvidenceLedger(); + const mutation = createEvidence(TARGET, PROVIDER, { + predicateType: "rea.residual-unknown-mutation", + operation: "record_unknown", + parameters: {}, + result: { action: "record", question: "Which path is current?" }, + }); + const created = ledger.recordUnknown( + recordUnknownInputSchema.parse({ + question: "Which path is current?", + severity: "high", + domain: "fixture", + required_authority: null, + required_confidence: "derived", + required_environment: null, + recommended_probes: [], + relationships: [], + }), + mutation, + ); + expect(created.ok).toBe(true); + const relocatedMutation = createEvidence( + { ...TARGET, path: "/relocated/mutation" }, + PROVIDER, + { + predicateType: "rea.residual-unknown-mutation", + operation: "record_unknown", + parameters: {}, + result: { action: "record", question: "Which path is current?" }, + }, + ); + expect(relocatedMutation.evidence_id).toBe(mutation.evidence_id); + const bundle = createEvidenceBundle( + [relocatedMutation], + ledger.export().unknowns, + ); + + expect(ledger.import(bundle)).toEqual({ + ok: true, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: true, + metadataChanged: true, + }, + }); + expect(ledger.get(mutation.evidence_id)).toEqual(relocatedMutation); + expect(ledger.import(bundle)).toEqual({ + ok: true, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: false, + metadataChanged: false, + }, + }); + }); it("retains more than ten thousand inline evidence records", () => { const ledger = new EvidenceLedger(); @@ -128,7 +240,6 @@ describe("evidence bundle imports", () => { result: { action: "record" }, }); const unknown = recordUnknownInputSchema.parse({ - approved: true, question: "What remains unresolved?", severity: "high", domain: "atomic-test", diff --git a/src/application/investigation/EvidenceLedger.ts b/src/application/investigation/EvidenceLedger.ts index 3b87c93d4..a75760e80 100644 --- a/src/application/investigation/EvidenceLedger.ts +++ b/src/application/investigation/EvidenceLedger.ts @@ -27,6 +27,7 @@ export interface EvidenceImportDelta { readonly recordsAdded: number; readonly unknownsAdded: number; readonly changed: boolean; + readonly metadataChanged: boolean; } type ImportResult = Result; @@ -70,11 +71,50 @@ export class EvidenceLedger { }), ); } + // A record-only bundle cannot change the already-valid unknown graph. The + // bundle parser has validated every incoming record and its manifests, so + // checking ID conflicts and committing only the additions preserves the + // atomic merge without rebuilding and reparsing the entire ledger. + if (bundle.unknowns.length === 0) { + const additions = new Map(); + const replacements = new Map(); + let metadataChanged = false; + for (const evidence of bundle.records) { + const existing = this.#records.get(evidence.evidence_id); + if (existing !== undefined) { + if (!recordsAgree(existing, evidence)) + return err( + new EvidenceIntegrityError("Conflicting evidence record"), + ); + if (canonicalize(existing) !== canonicalize(evidence)) { + replacements.set(evidence.evidence_id, evidence); + metadataChanged = true; + } + continue; + } + additions.set(evidence.evidence_id, evidence); + } + for (const [id, evidence] of replacements) + this.#records.set(id, evidence); + for (const [id, evidence] of additions) this.#records.set(id, evidence); + return ok({ + recordsAdded: additions.size, + unknownsAdded: 0, + changed: additions.size > 0 || metadataChanged, + metadataChanged, + }); + } const pending = new Map(this.#records); + let metadataChanged = false; for (const evidence of bundle.records) { const existing = pending.get(evidence.evidence_id); if (existing !== undefined && !recordsAgree(existing, evidence)) return err(new EvidenceIntegrityError("Conflicting evidence record")); + if ( + existing !== undefined && + canonicalize(existing) !== canonicalize(evidence) + ) + metadataChanged = true; pending.set(evidence.evidence_id, evidence); } const pendingUnknowns = new Map(this.#unknownRevisions); @@ -108,7 +148,8 @@ export class EvidenceLedger { return ok({ recordsAdded: added, unknownsAdded, - changed: added > 0 || unknownsAdded > 0, + changed: added > 0 || unknownsAdded > 0 || metadataChanged, + metadataChanged, }); } diff --git a/src/application/investigation/InvestigationRecords.test.ts b/src/application/investigation/InvestigationRecords.test.ts index 6450590ec..5ecf51e60 100644 --- a/src/application/investigation/InvestigationRecords.test.ts +++ b/src/application/investigation/InvestigationRecords.test.ts @@ -101,9 +101,18 @@ describe("investigation record ownership", () => { ); if (!created.ok) throw created.error; const input = updateUnknownInputSchema.parse({ - ...created.value, + unknown_id: created.value.unknown_id, expected_revision: created.value.revision, status: "investigating", + severity: created.value.severity, + supporting_evidence_ids: created.value.supporting_evidence_ids, + contradicting_evidence_ids: created.value.contradicting_evidence_ids, + required_authority: created.value.required_authority, + required_confidence: created.value.required_confidence, + required_environment: created.value.required_environment, + recommended_probes: created.value.recommended_probes, + relationships: created.value.relationships, + resolution: created.value.resolution, }); const updated = records.updateUnknown(input, target); expect(updated).toMatchObject({ @@ -133,11 +142,21 @@ describe("investigation record ownership", () => { ).toBe(true); expect(records.mergeEvidenceBundle(bundle)).toEqual({ ok: true, - value: { recordsAdded: 0, unknownsAdded: 1, changed: true }, + value: { + recordsAdded: 0, + unknownsAdded: 1, + changed: true, + metadataChanged: false, + }, }); expect(records.mergeEvidenceBundle(bundle)).toEqual({ ok: true, - value: { recordsAdded: 0, unknownsAdded: 0, changed: false }, + value: { + recordsAdded: 0, + unknownsAdded: 0, + changed: false, + metadataChanged: false, + }, }); records.clear(); expect(records.exportEvidenceBundle()).toEqual(createEvidenceBundle([])); diff --git a/src/application/javascript/JavaScriptApplicationEmptyLiterals.test.ts b/src/application/javascript/JavaScriptApplicationEmptyLiterals.test.ts index a3a425bf9..1503bf634 100644 --- a/src/application/javascript/JavaScriptApplicationEmptyLiterals.test.ts +++ b/src/application/javascript/JavaScriptApplicationEmptyLiterals.test.ts @@ -55,6 +55,24 @@ it.each([ await expect(analyzeSource(source)).resolves.toBeDefined(); }); +it("analyzes a plain long-line loader without package metadata", async () => { + const output = await analyzeSource( + `var Module = {}; fetch(""); new Worker(""); var payload = "${"x".repeat(65_536)}";`, + { "game-data.js": "Module.data = { ready: true };" }, + ); + const nodes = output.graph.nodes.filter( + ({ kind }) => kind === "endpoint" || kind === "worker", + ); + expect(nodes.map(({ kind }) => kind).sort()).toEqual(["endpoint", "worker"]); + for (const node of nodes) { + expect(node.identity).toMatchObject({ + strategy: "artifact-local-key", + key: "", + }); + expect(node.observations.map(({ label }) => label)).toEqual([null]); + } +}); + it("keeps exact values for empty literals without synthetic application labels", async () => { const output = await analyzeSource( "fetch(''); fetch('\"\"'); fetch('/api'); process.on('', () => 1);", diff --git a/src/application/javascript/JavaScriptApplicationService.test.ts b/src/application/javascript/JavaScriptApplicationService.test.ts index 5ff423b09..83e93e713 100644 --- a/src/application/javascript/JavaScriptApplicationService.test.ts +++ b/src/application/javascript/JavaScriptApplicationService.test.ts @@ -47,6 +47,8 @@ describe("JavaScript application failure diagnostics", () => { const inputPath = await createTestTempDirectory("rea-js-failure-"); await writeFile(join(inputPath, "main.js"), "export const value = 1;\n"); const cause = new RangeError("Invalid string length"); + // Retain the original failure locally, but never serialize its object graph. + cause.cause = cause; const result = await analyzeJavaScriptApplication( { input_path: inputPath, format: "directory" }, { @@ -72,6 +74,8 @@ describe("JavaScript application failure diagnostics", () => { }, }, }); + const projection = projectAnalysisError(result.error); + expect(JSON.parse(JSON.stringify(projection))).toEqual(projection); }); it("keeps filesystem failures distinct from engine failures", async () => { @@ -119,4 +123,35 @@ describe("JavaScript application failure diagnostics", () => { }); }, ); + + it.each([new Error("x".repeat(10_000)), "y".repeat(10_000)])( + "preserves long failure messages through JSON projection (%#)", + async (cause) => { + const inputPath = await createTestTempDirectory("rea-js-oversized-"); + await writeFile(join(inputPath, "main.js"), "export const value = 1;\n"); + const message = cause instanceof Error ? cause.message : cause; + const result = await analyzeJavaScriptApplication( + { input_path: inputPath, format: "directory" }, + { + progress: { + report: async (event) => { + if (event.terminal) throw cause; + }, + }, + }, + ); + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Expected analysis failure"); + expect(projectAnalysisError(result.error)).toMatchObject({ + code: "execution_failure", + details: { + diagnostics: { + error_message: message, + }, + }, + }); + const projection = projectAnalysisError(result.error); + expect(JSON.parse(JSON.stringify(projection))).toEqual(projection); + }, + ); }); diff --git a/src/application/javascript/JavaScriptApplicationService.ts b/src/application/javascript/JavaScriptApplicationService.ts index 6339379db..b1b180383 100644 --- a/src/application/javascript/JavaScriptApplicationService.ts +++ b/src/application/javascript/JavaScriptApplicationService.ts @@ -57,13 +57,27 @@ export const analyzeJavaScriptApplicationValidated = async ( format: input.format, }, options.signal, + options.progress, ); const { electron_summary: summary, ...application } = reconstructed; + await options.progress?.report({ + phase: "validate_javascript_application_result", + completed: 0, + total: 1, + message: "Validating the application analysis result", + }); const result = javascriptApplicationAnalysisResultSchema.parse({ ...application, summary, limitations: reconstructed.graph.limitations, }); + await options.progress?.report({ + phase: "create_javascript_application_evidence", + completed: 0, + total: 1, + message: "Creating and hashing application analysis Evidence", + }); + const evidence = createJavaScriptApplicationEvidence(input, result); await options.progress?.report({ phase: "analyze_javascript_application", completed: 1, @@ -71,7 +85,7 @@ export const analyzeJavaScriptApplicationValidated = async ( message: "Application graph and Electron boundaries reconstructed", terminal: true, }); - return ok(createJavaScriptApplicationEvidence(input, result)); + return ok(evidence); } catch (cause: unknown) { if (cause instanceof ArtifactReaderFailure) return err( diff --git a/src/application/javascript/JavaScriptArtifactGraphBuilder.ts b/src/application/javascript/JavaScriptArtifactGraphBuilder.ts index 6af56f1a7..49f317270 100644 --- a/src/application/javascript/JavaScriptArtifactGraphBuilder.ts +++ b/src/application/javascript/JavaScriptArtifactGraphBuilder.ts @@ -1,4 +1,4 @@ -import type { ArtifactInventorySnapshot } from "../ArtifactInventory.js"; +import type { ArtifactInventorySnapshot } from "../../artifacts/inventory/ArtifactInventory.js"; import { createJavaScriptApplicationGraph, type JavaScriptApplicationGraph, diff --git a/src/application/javascript/JavaScriptArtifactGraphContext.ts b/src/application/javascript/JavaScriptArtifactGraphContext.ts index 8e398bc77..291dfa46f 100644 --- a/src/application/javascript/JavaScriptArtifactGraphContext.ts +++ b/src/application/javascript/JavaScriptArtifactGraphContext.ts @@ -1,4 +1,4 @@ -import type { ArtifactInventorySnapshot } from "../ArtifactInventory.js"; +import type { ArtifactInventorySnapshot } from "../../artifacts/inventory/ArtifactInventory.js"; import { completeApplicationCoverage, partialApplicationCoverage, diff --git a/src/application/javascript/JavaScriptArtifactGraphStructure.ts b/src/application/javascript/JavaScriptArtifactGraphStructure.ts index 7a135fdea..039659f89 100644 --- a/src/application/javascript/JavaScriptArtifactGraphStructure.ts +++ b/src/application/javascript/JavaScriptArtifactGraphStructure.ts @@ -1,4 +1,4 @@ -import type { ArtifactInventorySnapshot } from "../ArtifactInventory.js"; +import type { ArtifactInventorySnapshot } from "../../artifacts/inventory/ArtifactInventory.js"; import type { ApplicationNode } from "../../domain/javascript/javascriptApplicationGraph.js"; import { completeApplicationCoverage } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js"; import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js"; diff --git a/src/application/javascript/JavaScriptArtifactPathResolution.test.ts b/src/application/javascript/JavaScriptArtifactPathResolution.test.ts index 5223926e8..0653ac8f7 100644 --- a/src/application/javascript/JavaScriptArtifactPathResolution.test.ts +++ b/src/application/javascript/JavaScriptArtifactPathResolution.test.ts @@ -9,6 +9,42 @@ import { artifactLocalIdentity } from "./JavaScriptArtifactGraphContext.js"; import { applicationNodeIdentitySchema } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js"; import { analyzeJavaScriptStaticSource } from "../../domain/javascript/javascriptStaticAnalysis.js"; +describe("artifact URI scheme classification", () => { + it.each(["html-reference", "module-specifier", "url-reference"] as const)( + "keeps digit-bearing schemes external in %s even when a local file matches", + (context) => { + const files = fileMap([ + file("index.html", "root"), + file("web3:app.js", "root"), + ]); + expect( + resolve({ + declaredPath: "web3:app.js", + sourcePath: "index.html", + context, + files, + }), + ).toMatchObject({ resolution_status: "external", resolved_path: null }); + }, + ); + + it("keeps references under a digit-bearing external base href external", () => { + const files = fileMap([ + file("index.html", "root"), + file("web3:assets/app.js", "root"), + ]); + expect( + resolve({ + declaredPath: "app.js", + sourcePath: "index.html", + context: "html-reference", + htmlBaseHref: "web3:assets/", + files, + }), + ).toMatchObject({ resolution_status: "external", resolved_path: null }); + }); +}); + describe("artifact-local graph identity", () => { it("preserves complete long namespace and key values", () => { const digest = "a".repeat(64); diff --git a/src/application/javascript/JavaScriptArtifactReconstruction.ts b/src/application/javascript/JavaScriptArtifactReconstruction.ts index 7acf03b05..7f14c7898 100644 --- a/src/application/javascript/JavaScriptArtifactReconstruction.ts +++ b/src/application/javascript/JavaScriptArtifactReconstruction.ts @@ -12,9 +12,10 @@ import { javascriptArtifactReconstructionInputSchema, type JavaScriptArtifactReconstructionInput, } from "./JavaScriptArtifactReconstructionInput.js"; -import { scanCanonicalArtifactInventory } from "../ArtifactInventory.js"; +import { scanCanonicalArtifactInventory } from "../../artifacts/inventory/ArtifactInventory.js"; import { summarizeElectronBoundaries } from "./ElectronBoundaryAnalysis.js"; import { buildJavaScriptSemanticGraph } from "./JavaScriptSemanticGraphBuilder.js"; +import type { ProgressReporter } from "../ProgressReporter.js"; /** Application-layer result retaining local diagnostics outside the canonical graph. */ export interface JavaScriptArtifactReconstructionResult { @@ -45,7 +46,11 @@ export interface JavaScriptArtifactReconstructionResult { export const reconstructJavaScriptArtifact = async ( rawInput: unknown, signal?: AbortSignal, + progress?: ProgressReporter, ): Promise => { + const reportPhase = async (phase: string, message: string): Promise => { + await progress?.report({ phase, completed: 0, total: 1, message }); + }; const input = javascriptArtifactReconstructionInputSchema.parse(rawInput); abortIfNeeded(signal); const path = await realpath(input.input_path); @@ -58,10 +63,26 @@ export const reconstructJavaScriptArtifact = async ( ); const reader = createReader(path, format); try { + await reportPhase( + "read_javascript_artifacts", + "Reading inventoried JavaScript application sources", + ); const files = await readJavaScriptArtifactFiles(reader, snapshot, signal); + await reportPhase( + "parse_javascript_sources", + `Parsing ${String(files.files.length)} application source files`, + ); const analysis = analyzeJavaScriptArtifactFiles(files); abortIfNeeded(signal); + await reportPhase( + "build_javascript_application_graph", + "Constructing application and Electron boundary relationships", + ); const graph = buildJavaScriptArtifactGraph(snapshot, files, analysis); + await reportPhase( + "build_javascript_semantic_graph", + "Constructing static semantic relationships", + ); const semanticGraph = buildJavaScriptSemanticGraph({ rootArtifactSha256: snapshot.manifest.root_sha256, applicationGraph: graph, diff --git a/src/application/javascript/JavaScriptModuleSpecifierPaths.test.ts b/src/application/javascript/JavaScriptModuleSpecifierPaths.test.ts index 63f44337e..ee5809771 100644 --- a/src/application/javascript/JavaScriptModuleSpecifierPaths.test.ts +++ b/src/application/javascript/JavaScriptModuleSpecifierPaths.test.ts @@ -20,37 +20,46 @@ const filesFor = (paths: readonly string[]) => ]), ); -const cases = ["#", "?"].flatMap((punctuation) => - (["require", "import", undefined] as const).flatMap((moduleKind) => - (["literal", "stripped", "both"] as const).map((layout) => ({ - punctuation, - moduleKind, - layout, - })), - ), -); - describe("module specifier punctuation", () => { - it.each(cases)( - "resolves $moduleKind with $punctuation and $layout files", - ({ punctuation, moduleKind, layout }) => { + it.each(["#", "?"])( + "keeps CommonJS punctuation and strips URL suffixes for ESM (%s)", + (punctuation) => { const literal = `app/dep.cjs${punctuation}literal.cjs`; const stripped = "app/dep.cjs"; - const files = filesFor([ - ...(layout !== "stripped" ? [literal] : []), - ...(layout !== "literal" ? [stripped] : []), - ]); - const expected = moduleKind === "require" ? literal : stripped; - const result = resolveArtifactPathByContext({ + const commonJs = resolveArtifactPathByContext({ declaredPath: `./dep.cjs${punctuation}literal.cjs`, sourcePath: "app/main.js", context: "module-specifier", - files, - ...(moduleKind === undefined ? {} : { moduleKind }), + files: filesFor([literal, stripped]), + moduleKind: "require", }); - expect(result).toMatchObject({ - resolution_status: files.has(expected) ? "resolved" : "not-found", - resolved_path: files.has(expected) ? expected : null, + expect(commonJs).toMatchObject({ + resolution_status: "resolved", + resolved_path: literal, + }); + + const esm = resolveArtifactPathByContext({ + declaredPath: `./dep.cjs${punctuation}literal.cjs`, + sourcePath: "app/main.js", + context: "module-specifier", + files: filesFor([literal, stripped]), + moduleKind: "import", + }); + expect(esm).toMatchObject({ + resolution_status: "resolved", + resolved_path: stripped, + }); + + const noStrippedTarget = resolveArtifactPathByContext({ + declaredPath: `./dep.cjs${punctuation}literal.cjs`, + sourcePath: "app/main.js", + context: "module-specifier", + files: filesFor([literal]), + moduleKind: "import", + }); + expect(noStrippedTarget).toMatchObject({ + resolution_status: "not-found", + resolved_path: null, }); }, ); diff --git a/src/application/javascript/JavaScriptReturnShapeProjection.test.ts b/src/application/javascript/JavaScriptReturnShapeProjection.test.ts deleted file mode 100644 index 11d91c02f..000000000 --- a/src/application/javascript/JavaScriptReturnShapeProjection.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { analyzeJavaScriptSemantics } from "../../domain/javascript/javascriptSemanticAnalysis.js"; -import { completeApplicationCoverage } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js"; -import { projectJavaScriptExportReturnShapes } from "./JavaScriptReturnShapeProjection.js"; - -const projectReturns = (body: string) => { - const ir = analyzeJavaScriptSemantics(`export function result() { ${body} }`); - const link = ir.moduleLinks.find( - ({ exportedName }) => exportedName === "result", - ); - if (link === undefined) throw new Error("Expected the result export"); - const projected = projectJavaScriptExportReturnShapes({ - ir, - link, - modulePath: "fixture.js", - baseCoverage: completeApplicationCoverage(), - }); - if (projected === null) throw new Error("Expected exported return shapes"); - return projected; -}; - -const unknownBranchField = { - path: "", - state: "unknown", - value: null, - reason: "Branches have incompatible values.", -}; - -describe("exported primitive union return shapes", () => { - const conditionalCases = [ - "missing", - "unsupported()", - '({ key: "value" })', - '["value"]', - '+"invalid"', - ].flatMap((other) => [ - `condition ? (choice ? "one" : "two") : ${other}`, - `condition ? ${other} : (choice ? "one" : "two")`, - ]); - const logicalCases = [ - { operator: "||", known: '(choice ? "" : "one")' }, - { operator: "&&", known: '(choice ? 0 : "one")' }, - { operator: "??", known: '(choice ? null : "one")' }, - ].flatMap(({ operator, known }) => [ - `${known} ${operator} missing`, - `missing ${operator} ${known}`, - ]); - - it.each([...conditionalCases, ...logicalCases])( - "projects an unknown field instead of a definite union for %s", - (expression) => { - const projected = projectReturns(`return ${expression};`); - - expect(projected.properties.static_return_shapes).toEqual([ - { - source_range: expect.any(Object), - value_status: "ambiguous", - fields: [unknownBranchField], - property_coverage: [], - }, - ]); - // Complete return-site collection does not make its values known. - expect(projected.properties.return_shape_coverage).toEqual({ - status: "complete", - retained_return_sites: 1, - omitted_return_sites: 0, - omitted_fields: 0, - omitted_property_coverage: 0, - projection_complete: true, - }); - expect(projected.coverage).toEqual(completeApplicationCoverage()); - expect(projected.limitations).toContain( - "Return shapes are inferred from inert syntax and do not prove runtime behavior.", - ); - }, - ); - - it("keeps an incompatible object field unknown beside a known literal", () => { - const projected = projectReturns(` - return { - kind: "result", - value: condition ? (choice ? "one" : "two") : missing, - }; - `); - expect(projected.properties.static_return_shapes).toEqual([ - { - source_range: expect.any(Object), - value_status: "object", - fields: [ - { path: "/kind", state: "literal", value: "result", reason: null }, - { ...unknownBranchField, path: "/value" }, - ], - property_coverage: [{ path: "", status: "complete", omitted: 0 }], - }, - ]); - }); - - it("retains valid nested unions, duplicates, and null alternatives", () => { - const projected = projectReturns(` - return condition - ? (choice ? "two" : "one") - : (other ? "one" : null); - `); - expect(projected.properties.static_return_shapes).toEqual([ - { - source_range: expect.any(Object), - value_status: "union", - fields: [ - { - path: "", - state: "union", - value: [null, "one", "two"], - reason: null, - }, - ], - property_coverage: [], - }, - ]); - }); - - it("preserves three independent direct returns without merging their values", () => { - const projected = projectReturns(` - if (condition) return choice ? "one" : "two"; - if (other) return missing; - return { kind: "object" }; - `); - expect(projected.properties.static_return_shapes).toEqual([ - { - source_range: expect.any(Object), - value_status: "union", - fields: [ - { path: "", state: "union", value: ["one", "two"], reason: null }, - ], - property_coverage: [], - }, - { - source_range: expect.any(Object), - value_status: "unknown", - fields: [ - { - path: "", - state: "unknown", - value: null, - reason: "Unbound identifier missing.", - }, - ], - property_coverage: [], - }, - { - source_range: expect.any(Object), - value_status: "object", - fields: [ - { path: "/kind", state: "literal", value: "object", reason: null }, - ], - property_coverage: [{ path: "", status: "complete", omitted: 0 }], - }, - ]); - expect(projected.properties.return_shape_coverage).toMatchObject({ - status: "complete", - retained_return_sites: 3, - omitted_return_sites: 0, - projection_complete: true, - }); - }); -}); diff --git a/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts b/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts index e7d1c1240..2707b06d2 100644 --- a/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts +++ b/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts @@ -1,6 +1,15 @@ import { expect, it } from "vitest"; -import { buildJavaScriptSemanticGraph } from "./JavaScriptSemanticGraphBuilder.js"; +import { + buildJavaScriptSemanticGraph, + SEMANTIC_GRAPH_FILE_NODE_CEILING, + SEMANTIC_GRAPH_NODE_CEILING, +} from "./JavaScriptSemanticGraphBuilder.js"; +import { + addSemanticGraphNode, + constructSemanticGraphNode, + createSemanticGraphProjectionState, +} from "./JavaScriptSemanticGraphConstruction.js"; import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js"; import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js"; import { queryJavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticQuery.js"; @@ -287,6 +296,31 @@ const graphFor = (source: string) => { }); }; +const semanticNodeFor = (roleKey: string) => { + const file: JavaScriptArtifactFile = { + path: "budget.js", + container_sha256: SHA256, + sha256: SHA256, + bytes: 0, + inventory_artifact_id: `art_${SHA256}`, + kind: "javascript", + unpacked: false, + text: { included: true, value: "" }, + }; + const state = createSemanticGraphProjectionState({ nodes: [] }); + return constructSemanticGraphNode( + file, + { + kind: "expression", + roleKey, + location: null, + label: roleKey, + functionNodeId: null, + }, + state, + ); +}; + it.each([ "const value = consume(Promise.resolve(1));", "async function run() { await consume(Promise.resolve(1)); }", @@ -428,3 +462,75 @@ it.each([ expect(query.relations).toContainEqual(relation); expect(query.nodes.map(({ node_id }) => node_id)).toContain(promise?.node_id); }); + +it("bounds semantic node projection and reports the ceiling in coverage", () => { + // A bundled vendor library can emit tens of thousands of semantic nodes on its + // own (measured: 135,286 from one minified editor bundle). Without a ceiling the + // projection exhausts the heap and the result cannot be serialized. The bound + // must be reported through coverage rather than dropping data silently. + const statements = Array.from( + { length: 4_000 }, + (_, index) => + `const value${index} = { a: ${index}, b: [${index}, ${index + 1}] };`, + ).join("\n"); + const graph = graphFor(statements); + + expect(graph.nodes.length).toBeLessThanOrEqual(SEMANTIC_GRAPH_NODE_CEILING); + expect(graph.coverage.truncated).toBe(true); + expect(graph.coverage.status).toBe("partial"); + expect(graph.coverage.omitted_nodes).toBeNull(); + expect(graph.coverage.omitted_relations).toBeNull(); + expect( + graph.coverage.families.every( + ({ omitted_relations }) => omitted_relations === null, + ), + ).toBe(true); + expect( + graph.coverage.limits.some( + ({ name }) => name === "semantic_graph_node_ceiling", + ), + ).toBe(true); +}); + +it("leaves a small application complete and untruncated", () => { + const graph = graphFor("const answer = 40 + 2;"); + expect(graph.coverage.truncated).toBe(false); + expect(graph.coverage.omitted_nodes).toBe(0); + expect(graph.coverage.omitted_relations).toBe(0); + expect(graph.coverage.limits).toEqual([]); +}); + +it("does not mark a file truncated when it exactly fills its budget", () => { + // Each unbound reference projects one expression, plus the module root. + const graph = graphFor( + "external;\n".repeat(SEMANTIC_GRAPH_FILE_NODE_CEILING - 1), + ); + expect(graph.nodes).toHaveLength(SEMANTIC_GRAPH_FILE_NODE_CEILING); + expect(graph.coverage).toMatchObject({ + truncated: false, + omitted_nodes: 0, + omitted_relations: 0, + limits: [], + }); +}); + +it("only records node loss when the budget rejects a new identity", () => { + // The truncation signal must record blocked node creation, not a zero + // remaining budget: a file using exactly its share drops nothing. + const state = createSemanticGraphProjectionState({ nodes: [] }); + state.fileNodeBudget = 2; + state.fileNodesDropped = false; + const first = addSemanticGraphNode(state, semanticNodeFor("first")); + const second = addSemanticGraphNode(state, semanticNodeFor("second")); + expect(first).not.toBeNull(); + expect(second).not.toBeNull(); + expect(state.fileNodeBudget).toBe(0); + expect(state.fileNodesDropped).toBe(false); + + expect(addSemanticGraphNode(state, semanticNodeFor("first"))).toBe(first); + expect(state.fileNodesDropped).toBe(false); + + const blocked = addSemanticGraphNode(state, semanticNodeFor("third")); + expect(blocked).toBeNull(); + expect(state.fileNodesDropped).toBe(true); +}); diff --git a/src/application/javascript/JavaScriptSemanticGraphBuilder.ts b/src/application/javascript/JavaScriptSemanticGraphBuilder.ts index d82edde74..38a352234 100644 --- a/src/application/javascript/JavaScriptSemanticGraphBuilder.ts +++ b/src/application/javascript/JavaScriptSemanticGraphBuilder.ts @@ -74,7 +74,35 @@ interface FileContext extends SemanticFlowProjectionContext { readonly callResolutions: ReadonlyMap; } -/** Project per-file semantic IR into an artifact-bound companion graph. */ +/** + * Resource-safety ceiling on retained semantic nodes. + * + * The semantic projection emits one node per AST expression, call site, binding + * and property slot. That is unbounded in input size. A small tree of five files + * already yields on the order of 170,000 nodes, and larger trees yield millions, + * exhausting the heap before the result can be serialized. + * + * This is a real resource-safety constraint, not a presentation preference, so + * the ceiling is applied and reported through the graph's own `coverage` fields + * (`truncated`, `omitted_nodes`, `omitted_relations`, `limits`) rather than + * silently dropping data. + * + * Sizing: a retained semantic node costs roughly 3 KB once serialized, and V8 + * additionally caps a single string at 512 MB. The result is serialized after the + * object graph is built and validated, so both phases must fit together. A single + * minified vendor bundle can account for most of a tree's nodes, so a whole-tree + * ceiling without a per-file ceiling does not bound the work. Both are applied. + */ +export const SEMANTIC_GRAPH_NODE_CEILING = 100_000; + +/** + * Per-file share of the semantic node budget. + * + * Bound each source file independently so one large bundled library cannot + * consume the whole tree's budget and starve the application's own modules. + */ +export const SEMANTIC_GRAPH_FILE_NODE_CEILING = 20_000; + export const buildJavaScriptSemanticGraph = ({ rootArtifactSha256, applicationGraph, @@ -82,11 +110,27 @@ export const buildJavaScriptSemanticGraph = ({ }: BuilderInput): JavaScriptSemanticGraph => { const state = emptyState(applicationGraph); const fingerprints: JavaScriptSemanticGraph["fingerprints"][number][] = []; + let truncatedFiles = 0; for (const analyzed of analysis.files) { if (analyzed.semantic === null) continue; + if (state.nodes.size >= SEMANTIC_GRAPH_NODE_CEILING) { + truncatedFiles += 1; + continue; + } + const remainingTreeBudget = SEMANTIC_GRAPH_NODE_CEILING - state.nodes.size; + state.fileNodeBudget = Math.min( + SEMANTIC_GRAPH_FILE_NODE_CEILING, + remainingTreeBudget, + ); + state.fileNodesDropped = false; fingerprints.push( ...projectFile(analyzed.file, analyzed.semantic.ir, state), ); + // Nodes were dropped only if the budget actually blocked creation. A file + // that exactly fills its share ends with a zero budget without dropping + // anything, so the remaining budget alone cannot decide truncation. + if (state.fileNodesDropped) truncatedFiles += 1; + state.fileNodeBudget = null; } if (state.roots.size === 0) addFallbackRoot(rootArtifactSha256, state); const unknowns = [...state.unknowns.values()]; @@ -100,11 +144,20 @@ export const buildJavaScriptSemanticGraph = ({ fingerprints, unknowns, coverage: { - status: "unknown", - truncated: false, - omitted_nodes: 0, - omitted_relations: 0, - limits: [], + status: truncatedFiles > 0 ? "partial" : "unknown", + truncated: truncatedFiles > 0, + omitted_nodes: truncatedFiles > 0 ? null : 0, + omitted_relations: truncatedFiles > 0 ? null : 0, + limits: + truncatedFiles > 0 + ? [ + { + name: "semantic_graph_node_ceiling", + value: SEMANTIC_GRAPH_NODE_CEILING, + unit: "items" as const, + }, + ] + : [], families: JAVASCRIPT_SEMANTIC_RELATION_FAMILIES.map((family) => ({ family, status: semanticFamilyStatus(family, analysis), @@ -112,7 +165,7 @@ export const buildJavaScriptSemanticGraph = ({ (relation) => JAVASCRIPT_SEMANTIC_RELATION_FAMILY[relation.relation] === family, ).length, - omitted_relations: 0, + omitted_relations: truncatedFiles > 0 ? null : 0, unknown_ids: unknowns .filter((unknown) => unknown.family === family) .map(({ unknown_id: identifier }) => identifier), diff --git a/src/application/javascript/JavaScriptSemanticGraphConstruction.ts b/src/application/javascript/JavaScriptSemanticGraphConstruction.ts index a19f74982..d69d9e736 100644 --- a/src/application/javascript/JavaScriptSemanticGraphConstruction.ts +++ b/src/application/javascript/JavaScriptSemanticGraphConstruction.ts @@ -26,6 +26,24 @@ export interface SemanticGraphProjectionState { readonly unknowns: Map; readonly roots: Set; readonly applicationNodeIdsByLocation: ReadonlyMap; + /** + * Remaining node budget for the file currently being projected. + * + * The projection emits one node per AST expression, call site, binding and + * property slot, which is unbounded in file size: one bundled vendor library + * measured 135,286 nodes on its own. `null` means unbounded; a number is the + * number of nodes this file may still add. Exhausting it stops node creation + * for that file and is reported through the graph's `coverage` fields. + */ + fileNodeBudget?: number | null; + /** + * Whether the file currently being projected lost any nodes to its budget. + * + * A file that exactly fills its share ends with a zero budget without + * dropping anything, so callers must consult this flag rather than the + * remaining budget to decide whether coverage is truncated. + */ + fileNodesDropped?: boolean; } /** Input for one exact artifact-version semantic node. */ @@ -90,6 +108,14 @@ export const addSemanticGraphNode = ( ): JavaScriptSemanticGraphNode | null => { const existing = state.nodes.get(node.node_id); if (existing !== undefined) return existing; + const budget = state.fileNodeBudget; + if (typeof budget === "number") { + if (budget <= 0) { + state.fileNodesDropped = true; + return null; + } + state.fileNodeBudget = budget - 1; + } state.nodes.set(node.node_id, node); return node; }; diff --git a/src/application/ProcessCli.ts b/src/application/process/ProcessCli.ts similarity index 87% rename from src/application/ProcessCli.ts rename to src/application/process/ProcessCli.ts index 82c9c83e5..8769edad8 100644 --- a/src/application/ProcessCli.ts +++ b/src/application/process/ProcessCli.ts @@ -1,18 +1,18 @@ import { readFile } from "node:fs/promises"; -import { AnalysisError } from "../domain/analysisErrorBase.js"; -import { AnalysisInputError } from "../domain/analysisErrorCore.js"; -import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; -import { createEvidence, parseEvidence } from "../domain/evidence.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; -import { processTraceSpecificationSchema } from "../domain/processTraceComparison.js"; -import { processScenarioSchema } from "../domain/processScenario.js"; +import { AnalysisError } from "../../domain/analysisErrorBase.js"; +import { AnalysisInputError } from "../../domain/analysisErrorCore.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; +import { createEvidence, parseEvidence } from "../../domain/evidence.js"; +import { jsonValueSchema } from "../../domain/jsonValue.js"; +import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { processTraceSpecificationSchema } from "../../domain/process/processTraceComparison.js"; +import { processScenarioSchema } from "../../domain/process/processScenario.js"; import { compareProcessCaptures, parseProcessCapture, -} from "../domain/processCapture.js"; -import { captureProcessScenario } from "./ProcessHarness.js"; +} from "../../domain/process/processCapture.js"; +import { captureProcessScenario } from "../../process/capture/ProcessHarness.js"; import { PROCESS_PROVIDER, createProcessCaptureEvidence, @@ -165,7 +165,11 @@ const readJson = async (path: string): Promise => { ); } try { - const parsed: unknown = JSON.parse(bytes.toString("utf8")); + const text = new TextDecoder("utf-8", { + fatal: true, + ignoreBOM: true, + }).decode(bytes); + const parsed: unknown = JSON.parse(text); return parsed; } catch (cause: unknown) { void cause; diff --git a/src/application/ProcessEvidence.ts b/src/application/process/ProcessEvidence.ts similarity index 76% rename from src/application/ProcessEvidence.ts rename to src/application/process/ProcessEvidence.ts index 50cd033ca..516eec478 100644 --- a/src/application/ProcessEvidence.ts +++ b/src/application/process/ProcessEvidence.ts @@ -1,13 +1,13 @@ -import type { JsonValue } from "../domain/jsonValue.js"; -import { createEvidence } from "../domain/evidence.js"; -import { PROCESS_PROVIDER } from "../domain/processEvidenceProvider.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; +import type { JsonValue } from "../../domain/jsonValue.js"; +import { createEvidence } from "../../domain/evidence.js"; +import { PROCESS_PROVIDER } from "../../domain/process/processEvidenceProvider.js"; +import { jsonValueSchema } from "../../domain/jsonValue.js"; import type { ProcessCapture, ProcessScenario, -} from "../domain/processCapture.js"; +} from "../../domain/process/processCapture.js"; -export { PROCESS_PROVIDER } from "../domain/processEvidenceProvider.js"; +export { PROCESS_PROVIDER } from "../../domain/process/processEvidenceProvider.js"; /** Project one process scenario into secret-free Evidence parameters. */ const processEvidenceParameters = ( diff --git a/src/artifacts/ArtifactProvider.ts b/src/artifacts/ArtifactProvider.ts index 54d9d3826..8d4885f48 100644 --- a/src/artifacts/ArtifactProvider.ts +++ b/src/artifacts/ArtifactProvider.ts @@ -9,8 +9,8 @@ import { } from "../application/AnalysisProvider.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 { inventoryArtifact } from "./inventory/ArtifactInventory.js"; +import { extractArtifact } from "./extraction/ArtifactExtraction.js"; import { analyzeInterfaceBuilderBundle } from "./apple/InterfaceBuilderAnalysis.js"; import { analyzeAppleAssetCatalogs } from "./apple/AppleAssetCatalogAnalysis.js"; import { @@ -33,7 +33,7 @@ import { } from "./ArtifactProviderMetadata.js"; import { createEvidence } from "../domain/evidence.js"; import { createArtifactInspection } from "../domain/artifactInspection.js"; -import { resolveArtifactIntegrityPolicy } from "../application/ArtifactInventory/policy.js"; +import { resolveArtifactIntegrityPolicy } from "./inventory/policy.js"; /** Read-only inventory and exclusively owned extraction provider. */ export class ArtifactProvider implements AnalysisProvider { diff --git a/src/artifacts/AsarArtifactReader.ts b/src/artifacts/AsarArtifactReader.ts index e231dae96..02ebb1bbe 100644 --- a/src/artifacts/AsarArtifactReader.ts +++ b/src/artifacts/AsarArtifactReader.ts @@ -1,17 +1,19 @@ -import { createReadStream } from "node:fs"; -import { sep } from "node:path"; +import { constants, createReadStream, type Stats } from "node:fs"; +import { lstat, open, realpath, type FileHandle } from "node:fs/promises"; +import { isAbsolute, join, relative, sep } from "node:path"; import { Readable } from "node:stream"; -import { extractFile, listPackage, statFile, uncache } from "@electron/asar"; +import { getRawHeader, listPackage, statFile, uncache } from "@electron/asar"; import { ArtifactReaderFailure, type ArtifactEntry, type ArtifactReader, } from "./ArtifactReader.js"; +import { closeAsarHandle, readValidatedAsarEntry } from "./AsarEntryStream.js"; /** - * Official Electron ASAR adapter. Individual files remain caller-bounded. + * Official Electron ASAR adapter with range-streamed member reads. * * An entry marked `unpacked` is metadata, not an integrity exemption: Electron * stores its bytes beside the archive in `.unpacked`, and callers must @@ -19,17 +21,33 @@ import { */ export class AsarArtifactReader implements ArtifactReader { readonly format = "asar" as const; + #archiveSize: number | undefined; + #headerSize: number | undefined; + readonly #entries = new Map(); - constructor(private readonly path: string) {} + constructor( + private readonly path: string, + private readonly openUnpackedFile: OpenAsarUnpackedFile = open, + ) {} async *entries(signal?: AbortSignal): AsyncIterable { let paths: string[]; try { // ASAR caches headers by path even after the archive has been replaced. + this.#resetArchiveState(); + this.#entries.clear(); uncache(this.path); paths = listPackage(this.path, { isPack: false }).sort((left, right) => left.localeCompare(right, "en"), ); + this.#headerSize = getRawHeader(this.path).headerSize; + const archiveMetadata = await lstat(this.path); + if (!archiveMetadata.isFile() || archiveMetadata.isSymbolicLink()) + throw new ArtifactReaderFailure( + "format", + "ASAR container is not a regular file", + ); + this.#archiveSize = archiveMetadata.size; } catch (cause: unknown) { throw asarFailure(this.path, "inventory", cause); } @@ -56,7 +74,7 @@ export class AsarArtifactReader implements ArtifactReader { : "link" in metadata ? "symlink" : "file"; - yield { + const entry: ArtifactEntry = { path, kind, declaredSize: "size" in metadata ? metadata.size : null, @@ -74,42 +92,121 @@ export class AsarArtifactReader implements ArtifactReader { limitations: kind === "symlink" ? ["ASAR symlink target was not followed or disclosed."] - : [ - "Official ASAR extraction buffers each individually bounded file.", - ], + : [], adapterKey: providerPath, }; + if (kind === "file" && isAsarFileMetadata(metadata)) + this.#entries.set(providerPath, { + metadata, + entry: copyArtifactEntry(entry), + }); + yield entry; } } - open(entry: ArtifactEntry, signal?: AbortSignal): Promise { + async open(entry: ArtifactEntry, signal?: AbortSignal): Promise { abortIfNeeded(signal); - if (entry.kind !== "file") - return Promise.reject( - new ArtifactReaderFailure("format", "ASAR entry is not a regular file"), + const state = this.#entries.get(entry.adapterKey); + if (state === undefined && entry.kind !== "file") + throw new ArtifactReaderFailure( + "format", + "ASAR entry is not a regular file", ); + if (state === undefined) + throw new ArtifactReaderFailure( + "integrity", + `ASAR entry was not produced by this reader: ${entry.path}`, + ); + if (!sameArtifactEntry(entry, state.entry)) + throw new ArtifactReaderFailure( + "integrity", + `ASAR entry metadata changed since inventory: ${state.entry.path}`, + ); + const { metadata } = state; + const producedEntry = state.entry; + if (producedEntry.unpacked) { + const handle = await this.#openUnpackedEntry(producedEntry); + try { + const observed = await handle.stat(); + abortIfNeeded(signal); + if (!observed.isFile()) + throw new ArtifactReaderFailure( + "path", + `ASAR unpacked entry is not a regular file: ${producedEntry.path}`, + ); + } catch (cause: unknown) { + await handle.close().catch(() => undefined); + throw asarFailure(this.path, `read ${producedEntry.path}`, cause); + } + const source = handle.createReadStream({ + start: 0, + autoClose: true, + }); + return readValidatedAsarEntry( + source, + undefined, + producedEntry.path, + this.path, + signal, + ); + } + const archiveSize = this.#archiveSize; + const headerSize = this.#headerSize; + const offset = + metadata.offset === undefined + ? undefined + : parseArchiveOffset(metadata.offset); + const start = + offset === undefined || headerSize === undefined + ? undefined + : 8 + headerSize + offset; + if ( + archiveSize === undefined || + start === undefined || + !Number.isSafeInteger(start) || + metadata.size < 0 || + !Number.isSafeInteger(metadata.size) || + start > archiveSize || + metadata.size > archiveSize - start + ) + throw new ArtifactReaderFailure( + "format", + `ASAR entry range is outside its container: ${producedEntry.path}`, + ); + let handle: FileHandle | undefined; try { - return Promise.resolve( - Readable.from(extractFile(this.path, entry.adapterKey, false)), + const openedHandle = await open( + this.path, + constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK, + ); + handle = openedHandle; + const observed = await openedHandle.stat(); + abortIfNeeded(signal); + if (!observed.isFile() || observed.size !== archiveSize) + throw new ArtifactReaderFailure( + "integrity", + `ASAR container changed before read: ${producedEntry.path}`, + ); + if (metadata.size === 0) { + await closeAsarHandle(() => openedHandle.close(), signal); + handle = undefined; + return Readable.from([]); + } + const source = openedHandle.createReadStream({ + start, + end: start + metadata.size - 1, + autoClose: true, + }); + return readValidatedAsarEntry( + source, + metadata.size, + producedEntry.path, + this.path, + signal, ); } catch (cause: unknown) { - if (entry.unpacked && isMissingFile(cause)) - return Promise.reject( - new ArtifactReaderFailure( - "unavailable", - `ASAR unpacked entry bytes are unavailable: ${entry.path}`, - { cause }, - { - logicalPath: entry.path, - declaredSha256: entry.declaredSha256, - calculatedSha256: null, - unpacked: true, - }, - ), - ); - return Promise.reject( - asarFailure(this.path, `read ${entry.path}`, cause), - ); + await handle?.close().catch(() => undefined); + throw asarFailure(this.path, `read ${producedEntry.path}`, cause); } } @@ -121,14 +218,154 @@ export class AsarArtifactReader implements ArtifactReader { close(): Promise { uncache(this.path); + this.#resetArchiveState(); + this.#entries.clear(); return Promise.resolve(); } provenance(): readonly [] { return []; } + + async #openUnpackedEntry(entry: ArtifactEntry): Promise { + const unpackedRoot = `${this.path}.unpacked`; + try { + const rootMetadata = await lstat(unpackedRoot); + if (!rootMetadata.isDirectory() || rootMetadata.isSymbolicLink()) + throw new ArtifactReaderFailure( + "path", + "ASAR unpacked companion is not a regular directory", + ); + const canonicalRoot = await realpath(unpackedRoot); + const candidate = join(unpackedRoot, entry.adapterKey); + const canonical = await realpath(candidate); + const relativePath = relative(canonicalRoot, canonical); + if ( + relativePath === ".." || + relativePath.startsWith(`..${sep}`) || + isAbsolute(relativePath) + ) + throw new ArtifactReaderFailure( + "path", + `ASAR unpacked entry escaped its companion directory: ${entry.path}`, + ); + const pathMetadata = await lstat(candidate); + if (!pathMetadata.isFile() || pathMetadata.isSymbolicLink()) + throw new ArtifactReaderFailure( + "path", + `ASAR unpacked entry is not a regular file: ${entry.path}`, + ); + const handle = await this.openUnpackedFile( + canonical, + constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK, + ); + const openedMetadata = await handle + .stat() + .catch(async (cause: unknown) => { + await handle.close().catch(() => undefined); + throw cause; + }); + if ( + !openedMetadata.isFile() || + openedMetadata.dev !== pathMetadata.dev || + openedMetadata.ino !== pathMetadata.ino + ) { + await handle.close(); + throw new ArtifactReaderFailure( + "integrity", + `ASAR unpacked entry changed before read: ${entry.path}`, + {}, + { + logicalPath: entry.path, + declaredSha256: entry.declaredSha256, + calculatedSha256: null, + unpacked: true, + }, + ); + } + return handle; + } catch (cause: unknown) { + if (cause instanceof ArtifactReaderFailure) throw cause; + if (entry.unpacked && isMissingFile(cause)) + throw new ArtifactReaderFailure( + "unavailable", + `ASAR unpacked entry bytes are unavailable: ${entry.path}`, + { cause }, + { + logicalPath: entry.path, + declaredSha256: entry.declaredSha256, + calculatedSha256: null, + unpacked: true, + }, + ); + throw asarFailure(this.path, `read ${entry.path}`, cause); + } + } + + #resetArchiveState(): void { + this.#archiveSize = undefined; + this.#headerSize = undefined; + } } +type AsarFileMetadata = Extract, { size: number }>; +type AsarEntryState = { + readonly metadata: AsarFileMetadata; + readonly entry: ArtifactEntry; +}; +type UnpackedFileHandle = { + stat(): Promise; + close(): Promise; + createReadStream: FileHandle["createReadStream"]; +}; +type OpenAsarUnpackedFile = ( + path: string, + flags: number, +) => Promise; + +const copyArtifactEntry = (entry: ArtifactEntry): ArtifactEntry => ({ + ...entry, + limitations: [...entry.limitations], + ...(entry.sourceIdentity === undefined + ? {} + : { sourceIdentity: { ...entry.sourceIdentity } }), +}); + +const sameArtifactEntry = ( + entry: ArtifactEntry, + produced: ArtifactEntry, +): boolean => + entry.path === produced.path && + entry.kind === produced.kind && + entry.declaredSize === produced.declaredSize && + entry.compressedSize === produced.compressedSize && + entry.executable === produced.executable && + entry.encrypted === produced.encrypted && + entry.byteOffset === produced.byteOffset && + entry.declaredSha256 === produced.declaredSha256 && + entry.unpacked === produced.unpacked && + entry.adapterKey === produced.adapterKey && + entry.limitations.length === produced.limitations.length && + entry.limitations.every( + (limitation, index) => limitation === produced.limitations[index], + ) && + entry.sourceIdentity?.device === produced.sourceIdentity?.device && + entry.sourceIdentity?.inode === produced.sourceIdentity?.inode; + +const isAsarFileMetadata = ( + metadata: ReturnType, +): metadata is AsarFileMetadata => + "size" in metadata && + typeof metadata.size === "number" && + !("files" in metadata) && + !("link" in metadata); + +const parseArchiveOffset = (value: string): number | undefined => { + if (!/^(?:0|[1-9][0-9]*)$/u.test(value)) return undefined; + const offset = Number(value); + return Number.isSafeInteger(offset) ? offset : undefined; +}; + const abortIfNeeded = (signal?: AbortSignal): void => { if (signal?.aborted === true) throw new ArtifactReaderFailure("cancelled", "ASAR operation cancelled"); diff --git a/src/artifacts/AsarEntryStream.ts b/src/artifacts/AsarEntryStream.ts new file mode 100644 index 000000000..b11b233d6 --- /dev/null +++ b/src/artifacts/AsarEntryStream.ts @@ -0,0 +1,111 @@ +import { Readable } from "node:stream"; + +import { ArtifactReaderFailure } from "./ArtifactReader.js"; + +/** Close an ASAR file handle before honoring cancellation of an empty member. */ +export const closeAsarHandle = async ( + close: () => Promise, + signal?: AbortSignal, +): Promise => { + await close(); + abortIfNeeded(signal); +}; + +/** Validate and contextualize bytes read from one ASAR member source. */ +export const readValidatedAsarEntry = ( + source: Readable, + expectedBytes: number | undefined, + entryPath: string, + archivePath: string, + signal?: AbortSignal, +): Readable => { + const output = Readable.from( + (async function* () { + let observedBytes = 0; + const iterator = source[Symbol.asyncIterator](); + try { + while (true) { + abortIfNeeded(signal); + const next = await iterator.next(); + if (next.done) break; + const chunk: unknown = next.value; + if (!Buffer.isBuffer(chunk) && !(chunk instanceof Uint8Array)) + throw new ArtifactReaderFailure( + "format", + `ASAR entry did not return bytes: ${entryPath}`, + ); + observedBytes += Buffer.isBuffer(chunk) + ? chunk.length + : chunk.byteLength; + if (expectedBytes !== undefined && observedBytes > expectedBytes) + throw new ArtifactReaderFailure( + "integrity", + `ASAR entry exceeded its declared size: ${entryPath}`, + ); + yield chunk; + } + if (expectedBytes !== undefined && observedBytes !== expectedBytes) + throw new ArtifactReaderFailure( + "integrity", + `ASAR entry size disagrees with its header: ${entryPath}`, + ); + } catch (cause: unknown) { + throw asarFailure(archivePath, `read ${entryPath}`, cause); + } finally { + try { + await iterator.return?.(); + } catch { + // Preserve the read failure; source destruction below owns cleanup. + } + if (!source.destroyed) source.destroy(); + } + })(), + ); + const closeSource = (): void => { + signal?.removeEventListener("abort", onAbort); + if (!source.destroyed) source.destroy(); + }; + const onAbort = (): void => { + closeSource(); + output.destroy( + new ArtifactReaderFailure("cancelled", "ASAR operation cancelled"), + ); + }; + output.once("close", closeSource); + signal?.addEventListener("abort", onAbort, { once: true }); + if (signal?.aborted === true) onAbort(); + return output; +}; + +const abortIfNeeded = (signal?: AbortSignal): void => { + if (signal?.aborted === true) + throw new ArtifactReaderFailure("cancelled", "ASAR operation cancelled"); +}; + +const asarFailure = ( + path: string, + operation: string, + cause: unknown, +): ArtifactReaderFailure => { + if (cause instanceof ArtifactReaderFailure) return cause; + if (isFilesystemFailure(cause)) + return new ArtifactReaderFailure( + "io", + `Could not ${operation} ASAR at ${path}: ${cause.message}`, + { cause }, + ); + return new ArtifactReaderFailure( + "format", + `Malformed ASAR during ${operation}: ${path}`, + { cause }, + ); +}; + +const isFilesystemFailure = ( + cause: unknown, +): cause is NodeJS.ErrnoException & Error => + cause instanceof Error && + "errno" in cause && + typeof cause.errno === "number" && + "syscall" in cause && + typeof cause.syscall === "string"; diff --git a/src/artifacts/DirectoryArtifactReader.ts b/src/artifacts/DirectoryArtifactReader.ts index cf4205c9e..1a6cf2657 100644 --- a/src/artifacts/DirectoryArtifactReader.ts +++ b/src/artifacts/DirectoryArtifactReader.ts @@ -13,34 +13,48 @@ import { export class DirectoryArtifactReader implements ArtifactReader { readonly format = "directory" as const; readonly #rootPromise: Promise; + readonly #rootPath: string; constructor(root: string) { + this.#rootPath = root; this.#rootPromise = realpath(root); } async *entries(signal?: AbortSignal): AsyncIterable { - const root = await this.#rootPromise; + const root = await this.#rootPromise.catch((cause: unknown) => { + throw directoryIoFailure("resolve artifact root", this.#rootPath, cause); + }); const pending = [root]; while (pending.length > 0) { abortIfNeeded(signal); const directory = pending.pop(); if (directory === undefined) break; - await assertContainedDirectory(root, directory); + await withDirectoryIoContext("inspect directory", directory, () => + assertContainedDirectory(root, directory), + ); const children: string[] = []; - const handle = await opendir(directory); + const handle = await opendir(directory).catch((cause: unknown) => { + throw directoryIoFailure("open directory", directory, cause); + }); try { for await (const child of handle) children.push(child.name); + } catch (cause: unknown) { + throw directoryIoFailure("read directory", directory, cause); } finally { // best-effort cleanup: directory-handle close must not mask traversal. await handle.close().catch(() => undefined); } - await assertContainedDirectory(root, directory); + await withDirectoryIoContext("inspect directory", directory, () => + assertContainedDirectory(root, directory), + ); children.sort((left, right) => left.localeCompare(right, "en")); const directories: string[] = []; for (const name of children) { abortIfNeeded(signal); const absolute = join(directory, name); - const metadata = await lstat(absolute); + const metadata = await lstat(absolute).catch((cause: unknown) => { + throw directoryIoFailure("inspect entry", absolute, cause); + }); const path = relative(root, absolute).split(sep).join("/"); const kind = metadata.isSymbolicLink() ? "symlink" @@ -86,9 +100,17 @@ export class DirectoryArtifactReader implements ArtifactReader { const handle = await open( entry.adapterKey, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK, - ); + ).catch((cause: unknown) => { + throw directoryIoFailure("open entry", entry.adapterKey, cause); + }); try { - const observed = await handle.stat(); + const observed = await handle.stat().catch((cause: unknown) => { + throw directoryIoFailure( + "inspect opened entry", + entry.adapterKey, + cause, + ); + }); if ( !observed.isFile() || entry.sourceIdentity === undefined || @@ -117,6 +139,38 @@ export class DirectoryArtifactReader implements ArtifactReader { } } +const directoryIoFailure = ( + operation: string, + path: string, + cause: unknown, +): ArtifactReaderFailure => { + if (cause instanceof ArtifactReaderFailure) return cause; + const code = + typeof cause === "object" && + cause !== null && + "code" in cause && + typeof cause.code === "string" + ? ` (${cause.code})` + : ""; + return new ArtifactReaderFailure( + "io", + `Could not ${operation} at ${path}${code}`, + { cause }, + ); +}; + +const withDirectoryIoContext = async ( + operation: string, + path: string, + action: () => Promise, +): Promise => { + try { + return await action(); + } catch (cause: unknown) { + throw directoryIoFailure(operation, path, cause); + } +}; + const abortIfNeeded = (signal?: AbortSignal): void => { if (signal?.aborted === true) throw new ArtifactReaderFailure( diff --git a/src/artifacts/NativeDmgArtifactReader.ts b/src/artifacts/NativeDmgArtifactReader.ts index b5c6ee73a..2c8caecc1 100644 --- a/src/artifacts/NativeDmgArtifactReader.ts +++ b/src/artifacts/NativeDmgArtifactReader.ts @@ -37,13 +37,18 @@ export interface NativeDmgHost { arguments_: readonly string[], signal?: AbortSignal, options?: { readonly timeoutMs: number }, - ): Promise<{ readonly stdout: string; readonly exitCode: number }>; + ): Promise<{ + readonly stdout: string; + readonly stderr?: string; + readonly exitCode: number; + readonly cause?: unknown; + }>; } const systemHost: NativeDmgHost = { async run(arguments_, signal, options) { try { - const { stdout } = await execFileOutput( + const { stdout, stderr } = await execFileOutput( "/usr/bin/hdiutil", [...arguments_], { @@ -51,13 +56,23 @@ const systemHost: NativeDmgHost = { ...(signal === undefined ? {} : { signal }), }, ); - return { stdout, exitCode: 0 }; + return { stdout, stderr, exitCode: 0 }; } catch (cause: unknown) { if (cause instanceof Error && cause.name === "AbortError") throw new ArtifactReaderFailure("cancelled", "DMG operation cancelled"); - throw new ArtifactReaderFailure("format", "hdiutil rejected the DMG", { - cause, - }); + const exitCode = processExitCode(cause); + if (exitCode !== undefined) + return { + stdout: processOutput(cause, "stdout"), + stderr: processOutput(cause, "stderr"), + exitCode, + cause, + }; + throw new ArtifactReaderFailure( + commandFailureReason(cause, arguments_[0]), + `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, + { cause }, + ); } }, }; @@ -250,15 +265,157 @@ const runChecked = async ( signal?: AbortSignal, options?: { readonly timeoutMs: number }, ): Promise<{ readonly stdout: string; readonly exitCode: 0 }> => { - const result = await host.run(arguments_, signal, options); - if (result.exitCode !== 0) + let result: Awaited>; + try { + result = await host.run(arguments_, signal, options); + } catch (cause: unknown) { + if (cause instanceof ArtifactReaderFailure) throw cause; + if (cause instanceof Error && cause.name === "AbortError") + throw new ArtifactReaderFailure("cancelled", "DMG operation cancelled", { + cause, + }); throw new ArtifactReaderFailure( - "unavailable", - `hdiutil ${arguments_[0] ?? "operation"} failed with a non-zero exit code`, + commandFailureReason(cause, arguments_[0]), + `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure( + cause, + arguments_, + )}`, + { cause }, ); + } + if (result.exitCode !== 0) { + const failure = { + command: "/usr/bin/hdiutil", + exitCode: result.exitCode, + ...(result.cause === undefined + ? {} + : { code: processExitCode(result.cause) }), + stdout: result.stdout, + ...(result.stderr === undefined ? {} : { stderr: result.stderr }), + }; + const reason = commandFailureReason(failure, arguments_[0]); + const details = describeCommandFailure(failure, arguments_); + const unknownVerifyFailure = + arguments_[0] === "verify" && reason === "unavailable"; + throw new ArtifactReaderFailure( + reason, + unknownVerifyFailure + ? `hdiutil verify failed; captured diagnostics do not establish the failure cause: ${details}` + : `hdiutil ${arguments_[0] ?? "operation"} failed: ${details}`, + result.cause === undefined ? undefined : { cause: result.cause }, + ); + } return { stdout: result.stdout, exitCode: 0 }; }; +const describeCommandFailure = ( + cause: unknown, + arguments_: readonly string[], +): string => { + const fields: Record = { + command: "/usr/bin/hdiutil", + arguments: [...arguments_], + }; + if (typeof cause === "object" && cause !== null) { + for (const key of [ + "code", + "exitCode", + "errno", + "syscall", + "signal", + "stdout", + "stderr", + ] as const) { + const value = Reflect.get(cause, key); + if (typeof value === "string" || typeof value === "number") + fields[key] = value; + } + const code = Reflect.get(cause, "code"); + if (typeof code === "number") fields.exitCode = code; + } + if (cause instanceof ArtifactReaderFailure) fields.message = cause.message; + else if (cause instanceof Error) fields.message = cause.message; + return JSON.stringify(fields); +}; + +const processExitCode = (cause: unknown): number | undefined => { + if (typeof cause !== "object" || cause === null) return undefined; + const exitCode = Reflect.get(cause, "exitCode"); + if (typeof exitCode === "number") return exitCode; + const code = Reflect.get(cause, "code"); + return typeof code === "number" ? code : undefined; +}; + +const processOutput = (cause: unknown, field: "stdout" | "stderr"): string => { + if (typeof cause !== "object" || cause === null) return ""; + const value = Reflect.get(cause, field); + return typeof value === "string" ? value : ""; +}; + +const commandFailureReason = ( + cause: unknown, + operation: string | undefined, +): ArtifactReaderFailure["reason"] => { + if (typeof cause !== "object" || cause === null) return "unavailable"; + const code = Reflect.get(cause, "code"); + const exitCode = Reflect.get(cause, "exitCode"); + if (typeof code === "number" || typeof exitCode === "number") + return operation === "verify" ? verifyFailureReason(cause) : "unavailable"; + if (code === "ENOENT") { + const syscall = Reflect.get(cause, "syscall"); + return typeof syscall === "string" && syscall.startsWith("spawn") + ? "unavailable" + : "io"; + } + if ( + code === "EACCES" || + code === "EPERM" || + code === "EIO" || + code === "ENOTDIR" || + code === "EISDIR" || + code === "ENODEV" || + code === "EROFS" || + code === "EMFILE" || + code === "ENFILE" + ) + return "io"; + return "unavailable"; +}; + +const verifyFailureReason = ( + cause: unknown, +): ArtifactReaderFailure["reason"] => { + const diagnostic = verifyFailureDiagnostic( + processOutput(cause, "stdout"), + processOutput(cause, "stderr"), + ); + switch (diagnostic) { + case "image not recognized": + return "format"; + case "invalid checksum": + case "image data corrupted": + return "integrity"; + case "No such file or directory": + case "Permission denied": + case "Input/output error": + return "io"; + default: + return "unavailable"; + } +}; + +const verifyFailureDiagnostic = ( + stdout: string, + stderr: string, +): string | undefined => { + const prefix = "hdiutil: verify failed - "; + return `${stdout}\n${stderr}` + .split(/\r?\n/u) + .map((line) => line.trim()) + .find((line) => line.startsWith(prefix)) + ?.slice(prefix.length); +}; + const command = ( arguments_: readonly string[], effects: ArtifactCommand["effects"], diff --git a/src/artifacts/ZipArtifactReader.ts b/src/artifacts/ZipArtifactReader.ts index f71a3b35b..8dd4471bd 100644 --- a/src/artifacts/ZipArtifactReader.ts +++ b/src/artifacts/ZipArtifactReader.ts @@ -53,6 +53,9 @@ export class ZipArtifactReader implements ArtifactReader { this.#reader = new ZipReader(this.#source, { checkSignature: true, checkOverlappingEntry: true, + // REA applies its own provider-neutral path validation in scanReader and + // must preserve that typed failure instead of zip.js rejecting first. + filenameValidation: "tolerant", }); } diff --git a/src/application/ArtifactExtraction.ts b/src/artifacts/extraction/ArtifactExtraction.ts similarity index 92% rename from src/application/ArtifactExtraction.ts rename to src/artifacts/extraction/ArtifactExtraction.ts index 7fc20dae9..57e8a0066 100644 --- a/src/application/ArtifactExtraction.ts +++ b/src/artifacts/extraction/ArtifactExtraction.ts @@ -1,29 +1,29 @@ import { lstat, realpath } from "node:fs/promises"; -import { canonicalDigest } from "../domain/comparisonSemantics.js"; -import { AsarArtifactReader } from "../artifacts/AsarArtifactReader.js"; +import { canonicalDigest } from "../../domain/comparisonSemantics.js"; +import { AsarArtifactReader } from "../AsarArtifactReader.js"; import { ArtifactPathRegistry, normalizeArtifactPath, -} from "../artifacts/ArtifactPaths.js"; +} from "../ArtifactPaths.js"; import { ArtifactReaderFailure, type ArtifactEntry, type ArtifactReader, -} from "../artifacts/ArtifactReader.js"; -import { DirectoryArtifactReader } from "../artifacts/DirectoryArtifactReader.js"; -import { SafeOutputTree } from "../artifacts/SafeOutputTree.js"; -import { ZipArtifactReader } from "../artifacts/ZipArtifactReader.js"; -import { MachOSliceArtifactReader } from "../artifacts/MachOSliceArtifactReader.js"; +} from "../ArtifactReader.js"; +import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js"; +import { SafeOutputTree } from "../SafeOutputTree.js"; +import { ZipArtifactReader } from "../ZipArtifactReader.js"; +import { MachOSliceArtifactReader } from "../MachOSliceArtifactReader.js"; import { artifactExtractionResultSchema, type ArtifactExtractionResult, type ArtifactGraphManifest, type ArtifactNode, type ArtifactOccurrence, -} from "../domain/artifactGraph.js"; -import type { BinaryTarget } from "../domain/binaryTarget.js"; -import { scanArtifactInventory } from "./ArtifactInventory.js"; +} from "../../domain/artifactGraph.js"; +import type { BinaryTarget } from "../../domain/binaryTarget.js"; +import { scanArtifactInventory } from "../inventory/ArtifactInventory.js"; /** Local extraction input with the output root chosen by the adapter. */ export interface ArtifactExtractionInput { diff --git a/src/artifacts/inventory/ArtifactGraphConstruction.test.ts b/src/artifacts/inventory/ArtifactGraphConstruction.test.ts new file mode 100644 index 000000000..93afadcb4 --- /dev/null +++ b/src/artifacts/inventory/ArtifactGraphConstruction.test.ts @@ -0,0 +1,14 @@ +import { expect, it } from "vitest"; + +import { compareDirectoryChildNames } from "./ArtifactGraphConstruction.js"; + +it("breaks locale-collation ties deterministically by code unit", () => { + const names = ["a\u200d.txt", "a.txt", "a\u200b.txt", "a\u200c.txt"]; + expect("a\u200d.txt".localeCompare("a.txt")).toBe(0); + expect(names.sort(compareDirectoryChildNames)).toEqual([ + "a.txt", + "a\u200b.txt", + "a\u200c.txt", + "a\u200d.txt", + ]); +}); diff --git a/src/application/ArtifactGraphConstruction.ts b/src/artifacts/inventory/ArtifactGraphConstruction.ts similarity index 97% rename from src/application/ArtifactGraphConstruction.ts rename to src/artifacts/inventory/ArtifactGraphConstruction.ts index 187d5d677..a0d1d5e1c 100644 --- a/src/application/ArtifactGraphConstruction.ts +++ b/src/artifacts/inventory/ArtifactGraphConstruction.ts @@ -1,13 +1,13 @@ -import { canonicalDigest } from "../domain/comparisonSemantics.js"; -import type { ArtifactEntry } from "../artifacts/ArtifactReader.js"; +import { canonicalDigest } from "../../domain/comparisonSemantics.js"; +import type { ArtifactEntry } from "../ArtifactReader.js"; import type { ArtifactCommand, ArtifactEdge, ArtifactNode, ArtifactOccurrence, -} from "../domain/artifactGraph.js"; -import { zipPackageFormatForPath } from "../domain/zipPackageFormat.js"; -import { mzWindowsHeaderOffset, parseDosMzHeader } from "../domain/dosMz.js"; +} from "../../domain/artifactGraph.js"; +import { zipPackageFormatForPath } from "../../domain/zipPackageFormat.js"; +import { mzWindowsHeaderOffset, parseDosMzHeader } from "../../domain/dosMz.js"; /** Mutable internal occurrence used until root-bound IDs are known. */ export interface MutableOccurrence { diff --git a/src/application/ArtifactInventory.ts b/src/artifacts/inventory/ArtifactInventory.ts similarity index 75% rename from src/application/ArtifactInventory.ts rename to src/artifacts/inventory/ArtifactInventory.ts index a7b5b181f..3b121766b 100644 --- a/src/application/ArtifactInventory.ts +++ b/src/artifacts/inventory/ArtifactInventory.ts @@ -3,22 +3,22 @@ import { realpath } from "node:fs/promises"; import { artifactInventoryResultSchema, type ArtifactInventoryResult, -} from "../domain/artifactGraph.js"; -import { abortIfNeeded } from "../artifacts/ArtifactHash.js"; -import { scanCanonicalArtifactInventory } from "./ArtifactInventory/scanCanonical.js"; +} from "../../domain/artifactGraph.js"; +import { abortIfNeeded } from "../ArtifactHash.js"; +import { scanCanonicalArtifactInventory } from "./scanCanonical.js"; import type { ArtifactIntegrityPolicy, ArtifactInventoryOptions, ArtifactInventorySnapshot, -} from "./ArtifactInventory/types.js"; +} from "./types.js"; -export { scanCanonicalArtifactInventory } from "./ArtifactInventory/scanCanonical.js"; +export { scanCanonicalArtifactInventory } from "./scanCanonical.js"; export type { ArtifactIntegrityPolicy, ArtifactInventoryOptions, ArtifactInventorySnapshot, -} from "./ArtifactInventory/types.js"; +} from "./types.js"; /** Inventory one local artifact and return every graph collection inline. */ export const inventoryArtifact = async ( diff --git a/src/application/ArtifactInventory/classify.ts b/src/artifacts/inventory/classify.ts similarity index 88% rename from src/application/ArtifactInventory/classify.ts rename to src/artifacts/inventory/classify.ts index 36e953712..421772346 100644 --- a/src/application/ArtifactInventory/classify.ts +++ b/src/artifacts/inventory/classify.ts @@ -1,7 +1,7 @@ import { open } from "node:fs/promises"; -import { classifyArtifactContent } from "../ArtifactGraphConstruction.js"; -import { ARTIFACT_CLASSIFICATION_PREFIX_BYTES } from "../../artifacts/ArtifactHash.js"; +import { classifyArtifactContent } from "./ArtifactGraphConstruction.js"; +import { ARTIFACT_CLASSIFICATION_PREFIX_BYTES } from "../ArtifactHash.js"; import type { ArtifactNode } from "../../domain/artifactGraph.js"; import { hasZipSignature, diff --git a/src/application/ArtifactInventory/policy.ts b/src/artifacts/inventory/policy.ts similarity index 100% rename from src/application/ArtifactInventory/policy.ts rename to src/artifacts/inventory/policy.ts diff --git a/src/application/ArtifactInventory/reader.ts b/src/artifacts/inventory/reader.ts similarity index 74% rename from src/application/ArtifactInventory/reader.ts rename to src/artifacts/inventory/reader.ts index f593acba0..18a0b2769 100644 --- a/src/application/ArtifactInventory/reader.ts +++ b/src/artifacts/inventory/reader.ts @@ -1,9 +1,9 @@ -import { AsarArtifactReader } from "../../artifacts/AsarArtifactReader.js"; -import type { ArtifactReader } from "../../artifacts/ArtifactReader.js"; -import { DirectoryArtifactReader } from "../../artifacts/DirectoryArtifactReader.js"; -import { MachOSliceArtifactReader } from "../../artifacts/MachOSliceArtifactReader.js"; -import { NativeDmgArtifactReader } from "../../artifacts/NativeDmgArtifactReader.js"; -import { ZipArtifactReader } from "../../artifacts/ZipArtifactReader.js"; +import { AsarArtifactReader } from "../AsarArtifactReader.js"; +import type { ArtifactReader } from "../ArtifactReader.js"; +import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js"; +import { MachOSliceArtifactReader } from "../MachOSliceArtifactReader.js"; +import { NativeDmgArtifactReader } from "../NativeDmgArtifactReader.js"; +import { ZipArtifactReader } from "../ZipArtifactReader.js"; import type { ArtifactNode } from "../../domain/artifactGraph.js"; export const createReader = async ( diff --git a/src/application/ArtifactInventory/scanCanonical.ts b/src/artifacts/inventory/scanCanonical.ts similarity index 98% rename from src/application/ArtifactInventory/scanCanonical.ts rename to src/artifacts/inventory/scanCanonical.ts index b02005394..2a746343d 100644 --- a/src/application/ArtifactInventory/scanCanonical.ts +++ b/src/artifacts/inventory/scanCanonical.ts @@ -5,7 +5,7 @@ import type { Stats } from "node:fs"; import { ArtifactReaderFailure, type ArtifactReader, -} from "../../artifacts/ArtifactReader.js"; +} from "../ArtifactReader.js"; import { artifactInventoryResultSchema, type ArtifactInventoryResult, @@ -20,10 +20,10 @@ import { rekeyOccurrences, rootOccurrenceFor, type MutableOccurrence, -} from "../ArtifactGraphConstruction.js"; +} from "./ArtifactGraphConstruction.js"; import { canonicalDigest } from "../../domain/comparisonSemantics.js"; import { classifyRoot } from "./classify.js"; -import { hashReadable, type HashResult } from "../../artifacts/ArtifactHash.js"; +import { hashReadable, type HashResult } from "../ArtifactHash.js"; import { createReader, inventoryLimitations } from "./reader.js"; import { scanReader, diff --git a/src/application/ArtifactInventory/scanReader.ts b/src/artifacts/inventory/scanReader.ts similarity index 96% rename from src/application/ArtifactInventory/scanReader.ts rename to src/artifacts/inventory/scanReader.ts index fe24fe408..136f527b4 100644 --- a/src/application/ArtifactInventory/scanReader.ts +++ b/src/artifacts/inventory/scanReader.ts @@ -3,13 +3,13 @@ import { isAbsolute } from "node:path"; import { ArtifactPathRegistry, normalizeArtifactPath, -} from "../../artifacts/ArtifactPaths.js"; +} from "../ArtifactPaths.js"; import { ArtifactReaderFailure, type ArtifactEntry, type ArtifactReader, -} from "../../artifacts/ArtifactReader.js"; -import { AsarArtifactReader } from "../../artifacts/AsarArtifactReader.js"; +} from "../ArtifactReader.js"; +import { AsarArtifactReader } from "../AsarArtifactReader.js"; import type { ArtifactNode } from "../../domain/artifactGraph.js"; import { classifyArtifactContent, @@ -17,12 +17,12 @@ import { createOccurrence, nearestParent, type MutableOccurrence, -} from "../ArtifactGraphConstruction.js"; +} from "./ArtifactGraphConstruction.js"; import { STRICT_INTEGRITY_POLICY, type ArtifactIntegrityPolicy, } from "./types.js"; -import { hashReadable } from "../../artifacts/ArtifactHash.js"; +import { hashReadable } from "../ArtifactHash.js"; export interface PendingIntegrityContradiction { readonly logicalPath: string; diff --git a/src/application/ArtifactInventory/types.ts b/src/artifacts/inventory/types.ts similarity index 100% rename from src/application/ArtifactInventory/types.ts rename to src/artifacts/inventory/types.ts diff --git a/src/artifacts/readStableArtifact.test.ts b/src/artifacts/readStableArtifact.test.ts new file mode 100644 index 000000000..6c02e1b98 --- /dev/null +++ b/src/artifacts/readStableArtifact.test.ts @@ -0,0 +1,62 @@ +import { + lstat, + mkdtemp, + open, + rm, + symlink, + unlink, + writeFile, +} from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { expect, it } from "vitest"; +import { readStableArtifact } from "./readStableArtifact.js"; + +const checkChange = async ( + stage: "before-open" | "after-read" | "symlink-replacement", +): Promise => { + const root = await mkdtemp(join(tmpdir(), "rea-stable-read-")); + const path = join(root, "selected"); + await writeFile(path, "original evidence"); + let inspected = 0; + try { + const reading = readStableArtifact(path, 1024, undefined, { + async lstat(selected) { + inspected++; + if (stage === "after-read" && inspected === 2) await unlink(selected); + const stat = await lstat(selected, { bigint: true }); + if (inspected === 1 && stage !== "after-read") { + await unlink(selected); + if (stage === "symlink-replacement") + await symlink("missing", selected); + } + return stat; + }, + open, + }); + await expect(reading).rejects.toMatchObject({ reason: "integrity" }); + } finally { + await rm(root, { recursive: true, force: true }); + } +}; + +it.each(["before-open", "after-read"] as const)( + "retains an actual post-validation %s change as integrity failure", + checkChange, +); + +it.skipIf(process.platform === "win32")( + "retains a post-validation symlink replacement as an integrity failure", + () => checkChange("symlink-replacement"), +); + +it("keeps initially missing inputs distinguishable from later changes", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-stable-missing-")); + try { + await expect( + readStableArtifact(join(root, "missing"), 1024), + ).rejects.toMatchObject({ code: "ENOENT" }); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/src/artifacts/readStableArtifact.ts b/src/artifacts/readStableArtifact.ts index 83280550e..560d00c95 100644 --- a/src/artifacts/readStableArtifact.ts +++ b/src/artifacts/readStableArtifact.ts @@ -1,16 +1,50 @@ import { createHash } from "node:crypto"; -import { constants } from "node:fs"; -import { lstat, open } from "node:fs/promises"; +import { constants, type BigIntStats } from "node:fs"; +import { lstat, open, type FileHandle } from "node:fs/promises"; import { ArtifactReaderFailure } from "./ArtifactReader.js"; +/** Filesystem boundary for stable reads, including deterministic replacement probes. */ +export interface StableArtifactFileSystem { + lstat(path: string): Promise; + open(path: string, flags: number): Promise; +} + +const FILE_SYSTEM: StableArtifactFileSystem = { + lstat: (path) => lstat(path, { bigint: true }), + open, +}; + +const afterValidation = async ( + path: string, + stage: string, + read: () => Promise, +): Promise => { + try { + return await read(); + } catch (cause) { + if ( + cause instanceof Error && + "code" in cause && + ["ENOENT", "ENOTDIR", "ELOOP"].includes(String(cause.code)) + ) + throw new ArtifactReaderFailure( + "integrity", + `Artifact changed ${stage}: ${path}`, + { cause }, + ); + throw cause; + } +}; + /** Read one bounded regular file and reject replacement or in-place changes. */ export const readStableArtifact = async ( path: string, maximumBytes: number, signal?: AbortSignal, + fileSystem: StableArtifactFileSystem = FILE_SYSTEM, ): Promise<{ readonly bytes: Buffer; readonly sha256: string }> => { signal?.throwIfAborted(); - const before = await lstat(path, { bigint: true }); + const before = await fileSystem.lstat(path); if (!before.isFile() || before.isSymbolicLink()) throw new ArtifactReaderFailure( "path", @@ -21,9 +55,11 @@ export const readStableArtifact = async ( "limit", `Selected artifact exceeds ${String(maximumBytes)} bytes: ${path}`, ); - const file = await open( - path, - constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK, + const file = await afterValidation(path, "before open", () => + fileSystem.open( + path, + constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK, + ), ); try { const initial = await file.stat({ bigint: true }); @@ -59,7 +95,9 @@ export const readStableArtifact = async ( chunks.push(bytes); } const after = await file.stat({ bigint: true }); - const current = await lstat(path, { bigint: true }); + const current = await afterValidation(path, "during read", () => + fileSystem.lstat(path), + ); if ( [after, current].some( (stat) => diff --git a/src/browser/CdpCaptureDocuments.baseUrl.test.ts b/src/browser/CdpCaptureDocuments.baseUrl.test.ts index 7b3dc1868..c2c9378b8 100644 --- a/src/browser/CdpCaptureDocuments.baseUrl.test.ts +++ b/src/browser/CdpCaptureDocuments.baseUrl.test.ts @@ -115,29 +115,6 @@ describe("DOM metadata document base URLs", () => { }); describe("empty form destinations with a document base URL", () => { - it.each([ - { nodeName: "FORM", attribute: "action" }, - { nodeName: "BUTTON", attribute: "formaction" }, - { nodeName: "INPUT", attribute: "formaction" }, - ])( - "resolves an empty $nodeName $attribute to the document", - ({ nodeName, attribute }) => { - const pageUrl = `${documentUrl}?selected=fixture#section`; - const baseUrl = `${origin}/assets/`; - const value = snapshot(baseUrl, "/assets/", pageUrl, ""); - value.strings[6] = nodeName; - value.strings[14] = attribute; - value.strings[15] = ""; - const result = capture(value); - expect(result.urls[3]).toMatchObject({ - attribute, - url: pageUrl, - destination_scope: "approved", - }); - expect(result.urls[1]?.url).toBe(baseUrl); - }, - ); - it("keeps an empty form action approved when the document base is foreign", () => { const foreign = "https://cdn.example.test/assets/"; const value = snapshot(foreign, foreign); diff --git a/src/browser/CdpCaptureEventHandlers.ts b/src/browser/CdpCaptureEventHandlers.ts index 0b9305366..23c06c293 100644 --- a/src/browser/CdpCaptureEventHandlers.ts +++ b/src/browser/CdpCaptureEventHandlers.ts @@ -10,7 +10,10 @@ import { cdpStringValue, type UnknownRecord, } from "./CdpCaptureValues.js"; -import { requestBodyShape } from "./CdpCaptureEventBodyShapes.js"; +import { + requestBodyShape, + updateResponseBodyShape, +} from "./CdpCaptureEventBodyShapes.js"; import { consolePrimitive, decodeBase64, @@ -19,10 +22,11 @@ import { firstCallFrame, initiatorLocation, integerOrNull, + isJsonMediaType, isMainFrameNavigation, } from "./CdpCaptureEventHelpers.js"; import type { CdpCaptureEventsState } from "./CdpCaptureEventState.js"; -import type { CapturedScript } from "./CdpCaptureEventTypes.js"; +import type { CapturedScript, NetworkState } from "./CdpCaptureEventTypes.js"; export const handleExecutionContextCreated = ( state: CdpCaptureEventsState, @@ -120,9 +124,11 @@ export const handleRequestWillBeSent = ( state.completeness.exclude("network_requests", "invalid_protocol_value"); return; } + if (state.malformedRedirectRequestIds.has(requestId)) return; if (request === undefined) { state.completeness.exclude("network_requests", "invalid_protocol_value"); state.network.delete(requestId); + state.networkRequestTimestamps.delete(requestId); return; } const sanitized = allowedSanitizedUrl(request.url, state.allowedOrigins); @@ -132,8 +138,60 @@ export const handleRequestWillBeSent = ( exclusionReasonForUrl(cdpStringValue(request.url)), ); state.network.delete(requestId); + state.networkRequestTimestamps.delete(requestId); return; } + const previous = state.network.get(requestId); + const redirectResponse = recordValue(params.redirectResponse); + if ( + Object.hasOwn(params, "redirectResponse") && + redirectResponse === undefined && + preserveMalformedRedirectEvidence(state, requestId, previous) + ) + return; + const redirects = [...(previous?.redirects ?? [])]; + if (redirectResponse !== undefined) { + const rawResponseUrl = cdpStringValue(redirectResponse.url); + const responseUrl = + rawResponseUrl === undefined || rawResponseUrl === "" + ? undefined + : allowedSanitizedUrl(rawResponseUrl, state.allowedOrigins); + const responseUrlReason = redirectResponseUrlReason(rawResponseUrl); + if (previous === undefined) { + if (responseUrl === undefined) + state.completeness.exclude("network_requests", responseUrlReason); + else + // CDP may deliver the continuation after its predecessor was excluded + // by origin policy or before capture began. Keep the final request but + // report missing prior coverage without calling it malformed protocol. + state.completeness.attachLimited("network_requests"); + } else if (responseUrl === undefined) { + // redirectResponse belongs to the same request ID and can contain an + // excluded origin. Drop the entire chain before retaining any details. + state.completeness.exclude("network_requests", responseUrlReason); + state.network.delete(requestId); + state.networkRequestTimestamps.delete(requestId); + return; + } else { + redirects.push({ + url: previous.url, + response_url: responseUrl.url, + method: previous.method, + resource_type: previous.resource_type, + status: numberValue(redirectResponse.status) ?? null, + mime_type: cdpStringValue(redirectResponse.mimeType) ?? null, + encoded_data_length: (() => { + const length = numberValue(redirectResponse.encodedDataLength); + return length === undefined ? null : Math.max(0, length); + })(), + request_timestamp: + state.networkRequestTimestamps.get(requestId) ?? null, + // CDP reports redirectResponse on the next requestWillBeSent event. + // This is the event boundary, not a separately observed response time. + redirect_event_timestamp: numberValue(params.timestamp) ?? null, + }); + } + } const initiator = recordValue(params.initiator); const initiatorFrame = initiatorLocation(initiator); const rawInitiatorUrl = cdpStringValue(initiatorFrame?.url); @@ -155,6 +213,7 @@ export const handleRequestWillBeSent = ( status: null, mime_type: null, encoded_data_length: null, + redirects, initiator: { type: cdpStringValue(initiator?.type) ?? "other", url: initiatorUrl?.url ?? null, @@ -163,6 +222,44 @@ export const handleRequestWillBeSent = ( }, body_shapes: requestBodyShape(state, request), }); + const timestamp = numberValue(params.timestamp); + if (timestamp === undefined) state.networkRequestTimestamps.delete(requestId); + else state.networkRequestTimestamps.set(requestId, timestamp); +}; + +const preserveMalformedRedirectEvidence = ( + state: CdpCaptureEventsState, + requestId: string, + previous: NetworkState | undefined, +): boolean => { + state.completeness.exclude("network_requests", "invalid_protocol_value"); + if (previous === undefined) return false; + state.malformedRedirectRequestIds.add(requestId); + if ( + state.input.include_json_body_shapes && + isJsonMediaType(previous.mime_type) && + previous.body_shapes.response === null + ) { + updateResponseBodyShape(state, requestId, null); + state.completeness.exclude("json_body_shapes", "invalid_protocol_value"); + } + return true; +}; + +const redirectResponseUrlReason = ( + value: string | undefined, +): + | "invalid_protocol_value" + | "disallowed_origin" + | "unsupported_url" + | "unattributed_origin" => { + if (value === undefined || value === "") return "invalid_protocol_value"; + try { + new URL(value); + } catch { + return "invalid_protocol_value"; + } + return exclusionReasonForUrl(value); }; export const handleResponseReceived = ( @@ -174,6 +271,7 @@ export const handleResponseReceived = ( state.completeness.exclude("network_requests", "invalid_protocol_value"); return; } + if (state.malformedRedirectRequestIds.has(requestId)) return; const current = state.network.get(requestId); const response = recordValue(params.response); if (current === undefined) return; @@ -186,6 +284,7 @@ export const handleResponseReceived = ( : exclusionReasonForUrl(cdpStringValue(response.url)), ); state.network.delete(requestId); + state.networkRequestTimestamps.delete(requestId); return; } state.network.set(requestId, { @@ -209,6 +308,7 @@ export const handleLoadingFinished = ( ): void => { const requestId = cdpStringValue(params.requestId); if (requestId === undefined) return; + if (state.malformedRedirectRequestIds.has(requestId)) return; const current = state.network.get(requestId); if (current === undefined) return; state.network.set(requestId, { diff --git a/src/browser/CdpCaptureEventState.ts b/src/browser/CdpCaptureEventState.ts index 654313961..acff1d535 100644 --- a/src/browser/CdpCaptureEventState.ts +++ b/src/browser/CdpCaptureEventState.ts @@ -11,6 +11,8 @@ export interface CdpCaptureEventsState { readonly scripts: Map; readonly executionContextFrames: Map; readonly network: Map; + readonly networkRequestTimestamps: Map; + readonly malformedRedirectRequestIds: Set; readonly allowedWebSockets: Set; console: WebPageInspection["console"]["events"]; websockets: WebPageInspection["network"]["websocket_events"]; diff --git a/src/browser/CdpCaptureEvents.redirectBodyShape.test.ts b/src/browser/CdpCaptureEvents.redirectBodyShape.test.ts new file mode 100644 index 000000000..fb2da70a3 --- /dev/null +++ b/src/browser/CdpCaptureEvents.redirectBodyShape.test.ts @@ -0,0 +1,66 @@ +import { expect, it } from "vitest"; + +import { CdpCaptureEvents } from "./CdpCaptureEvents.js"; +import { inspectWebPageInputSchema } from "../domain/browserObservation.js"; + +it("preserves an already captured response shape without marking it unavailable", () => { + const origin = "http://127.0.0.1:43127"; + const events = new CdpCaptureEvents( + inspectWebPageInputSchema.parse({ + cdp_endpoint: origin, + allowed_origins: [origin], + target_id: "allowed-page", + include_json_body_shapes: true, + }), + new Set([origin]), + ); + events.ingest({ + method: "Network.requestWillBeSent", + params: { + requestId: "request-1", + request: { + url: `${origin}/prior`, + method: "POST", + headers: { "Content-Type": "application/json" }, + postData: '{"request":true}', + }, + timestamp: 1, + }, + }); + events.ingest({ + method: "Network.responseReceived", + params: { + requestId: "request-1", + response: { + url: `${origin}/prior`, + status: 200, + mimeType: "application/json", + }, + }, + }); + events.ingestResponseBody("request-1", { + body: '{"response":true}', + base64Encoded: false, + }); + const capturedShape = events.network.get("request-1")?.body_shapes.response; + expect(capturedShape).not.toBeNull(); + + events.ingest({ + method: "Network.requestWillBeSent", + params: { + requestId: "request-1", + request: { url: `${origin}/continuation`, method: "GET" }, + redirectResponse: null, + timestamp: 2, + }, + }); + + expect(events.network.get("request-1")?.body_shapes.response).toEqual( + capturedShape, + ); + expect(events.network.get("request-1")?.body_shapes.status).toBe("included"); + expect(events.responseBodyRequestIds()).toEqual([]); + expect(events.completeness.snapshot().unavailable_sections).not.toContain( + "json_body_shapes", + ); +}); diff --git a/src/browser/CdpCaptureEvents.ts b/src/browser/CdpCaptureEvents.ts index e06a8722e..bb24433ee 100644 --- a/src/browser/CdpCaptureEvents.ts +++ b/src/browser/CdpCaptureEvents.ts @@ -18,6 +18,8 @@ export class CdpCaptureEvents implements CdpCaptureEventsState { readonly scripts = new Map(); readonly executionContextFrames = new Map(); readonly network = new Map(); + readonly networkRequestTimestamps = new Map(); + readonly malformedRedirectRequestIds = new Set(); readonly allowedWebSockets = new Set(); console: WebPageInspection["console"]["events"] = []; websockets: WebPageInspection["network"]["websocket_events"] = []; @@ -51,6 +53,8 @@ export class CdpCaptureEvents implements CdpCaptureEventsState { this.scripts.clear(); this.executionContextFrames.clear(); this.network.clear(); + this.networkRequestTimestamps.clear(); + this.malformedRedirectRequestIds.clear(); this.allowedWebSockets.clear(); this.console.length = 0; this.websockets.length = 0; @@ -135,16 +139,21 @@ export class CdpCaptureEvents implements CdpCaptureEventsState { responseBodyRequestIds(): readonly string[] { if (!this.input.include_json_body_shapes) return []; return [...this.network.values()] + .filter( + (request) => !this.malformedRedirectRequestIds.has(request.request_id), + ) .filter((request) => isJsonMediaType(request.mime_type)) .map((request) => request.request_id); } responseBodyUnavailable(requestId: string): void { + if (this.malformedRedirectRequestIds.has(requestId)) return; bodyShapes.updateResponseBodyShape(this, requestId, null); this.completeness.unavailable("json_body_shapes"); } ingestResponseBody(requestId: string, value: unknown): void { + if (this.malformedRedirectRequestIds.has(requestId)) return; bodyShapes.ingestResponseBodyShape(this, requestId, value); } } diff --git a/tests/boundary/browser/cdpSafeMetadata.test.ts b/src/browser/CdpSafeMetadata.test.ts similarity index 99% rename from tests/boundary/browser/cdpSafeMetadata.test.ts rename to src/browser/CdpSafeMetadata.test.ts index 56df3421e..0cff4e094 100644 --- a/tests/boundary/browser/cdpSafeMetadata.test.ts +++ b/src/browser/CdpSafeMetadata.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { safeResponseMetadata } from "../../../src/browser/CdpSafeMetadata.js"; +import { safeResponseMetadata } from "./CdpSafeMetadata.js"; const origin = "https://app.example.test"; diff --git a/src/browser/PlaywrightElectronActiveProvider.ts b/src/browser/PlaywrightElectronActiveProvider.ts index 903136908..2ca18b421 100644 --- a/src/browser/PlaywrightElectronActiveProvider.ts +++ b/src/browser/PlaywrightElectronActiveProvider.ts @@ -27,7 +27,11 @@ import { type ProcessCleanupResult, type ProcessLineageObservation, } from "../process/ProcessOwnership.js"; -import { observeOwnedProcessLineage } from "../process/ProcessOwnershipObservation.js"; +import { + observeOwnedProcessLineage, + prepareProcessOwnershipInspection, + systemProcessOwnershipHost, +} from "../process/ProcessOwnershipObservation.js"; import { selectCapturedProcessGroupIds } from "../process/ProcessOwnershipProcessTree.js"; import { runElectronActions, @@ -157,8 +161,13 @@ export class PlaywrightElectronActiveProvider implements ElectronActiveObservati try { if (options.signal?.aborted === true) throw new BrowserObservationError(OPERATION, "cancelled"); + await prepareProcessOwnershipInspection(options.signal); const startupDeadline = Date.now() + STARTUP_TIMEOUT_MS; const paths = await canonicalPaths(input); + const captureBaseline = + await systemProcessOwnershipHost.captureBaseline?.(options.signal); + if (options.signal?.aborted) + throw new BrowserObservationError(OPERATION, "cancelled"); application = await electron.launch({ executablePath: paths.executable, cwd: paths.root, @@ -183,6 +192,7 @@ export class PlaywrightElectronActiveProvider implements ElectronActiveObservati expectedParentPid: process.pid, expectedCommand: paths.executable, sweepTokenOwnedProcesses: true, + ...(captureBaseline === undefined ? {} : { captureBaseline }), }; const actions = await runElectronActions(application, input, options); const state = await runWithExecutionLimits( @@ -191,7 +201,13 @@ export class PlaywrightElectronActiveProvider implements ElectronActiveObservati ); outcome = ok(createResult(paths, input, actions, state)); } catch (cause: unknown) { - outcome = err(providerError(cause)); + outcome = err( + options.signal?.aborted === true && + (cause === options.signal.reason || + (cause instanceof Error && cause.name === "AbortError")) + ? new BrowserObservationError(OPERATION, "cancelled", { cause }) + : providerError(cause), + ); } if (application !== undefined) { if (ownership !== undefined) @@ -277,7 +293,11 @@ const cleanupElectronProcesses = async ( if (!result.cleaned) return result; signaled ||= result.signaled; } - const remaining = await verifyNoTokenOwnedProcesses(ownership.runId); + const remaining = await verifyNoTokenOwnedProcesses( + ownership.runId, + undefined, + ownership.captureBaseline, + ); if (!remaining.cleaned) return remaining; return { cleaned: true, signaled }; }; diff --git a/src/browser/PlaywrightScenarioActions.test.ts b/src/browser/PlaywrightScenarioActions.test.ts deleted file mode 100644 index 2acd4322d..000000000 --- a/src/browser/PlaywrightScenarioActions.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { expect, it } from "vitest"; -import type { Page } from "playwright-core"; - -import { BrowserScenarioSecrets } from "./BrowserScenarioSecrets.js"; -import { performPlaywrightScenarioAction } from "./PlaywrightScenarioActions.js"; -import { browserScenarioSchema } from "../domain/browserScenario.js"; - -it("uses exact accessible-name matching without a caller confirmation field", async () => { - const scenario = browserScenarioSchema.parse({ - browser: { mode: "launch", executable_path: "chrome" }, - start_url: { url: "https://app.example.test/" }, - actions: [ - { - step_id: "submit", - action: "click", - locator: { kind: "role", role: "button", name: "Submit" }, - }, - ], - }); - const calls: unknown[][] = []; - const page = { - getByRole: (...args: unknown[]) => { - calls.push(args); - return { click: async () => undefined }; - }, - } as unknown as Page; - const secrets = BrowserScenarioSecrets.resolve(scenario, {}); - if (secrets === undefined) throw new Error("No secret declarations expected"); - const action = scenario.actions.at(0); - if (action === undefined) throw new Error("Expected one scenario action"); - - await performPlaywrightScenarioAction({ - page, - action, - secrets, - }); - - expect(calls).toEqual([["button", { name: "Submit", exact: true }]]); -}); diff --git a/src/browser/history/CaptureFailures.test.ts b/src/browser/history/CaptureFailures.test.ts new file mode 100644 index 000000000..71026ed6c --- /dev/null +++ b/src/browser/history/CaptureFailures.test.ts @@ -0,0 +1,101 @@ +import { expect, it } from "vitest"; +import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; +import { analysisErrorProjectionSchema } from "../../contracts/errorSchemas.js"; +import { inspectWebNetworkCaptureInputSchema } from "../../domain/webNetworkCapture.js"; +import { historicalCaptureFailure } from "./CaptureFailures.js"; +import { redactExplicitFailure } from "../../domain/explicitSensitiveFailure.js"; +import { OwnedCommandFailure } from "../../process/OwnedCommand.js"; + +it.each(["before open", "during read"])( + "preserves observed capture changes %s as retryable acquisition integrity failures", + (stage) => { + const path = "/selected/private-marker.har"; + const input = inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + }); + const cause = new ArtifactReaderFailure( + "integrity", + `Artifact changed ${stage}: ${path}`, + ); + const error = historicalCaptureFailure(input, cause, "capture-read"); + const projected = projectAnalysisError(error); + expect(analysisErrorProjectionSchema.parse(projected)).toMatchObject({ + code: "artifact_changed", + category: "integrity_mismatch", + retryable: true, + details: { + operation: "inspect_web_network_capture", + path, + boundary: "stable-artifact-read", + reason: cause.message, + }, + }); + expect(projected.remediation.action).toContain("stable"); + const redacted = projectAnalysisError( + redactExplicitFailure(error, ["private-marker", "REDACTED"]), + ); + expect(redacted.code).toBe("artifact_changed"); + expect(JSON.stringify(redacted)).not.toContain("private-marker"); + expect(JSON.stringify(redacted)).not.toContain("REDACTED"); + }, +); + +it("retains actual command status and both diagnostic streams when cleanup also fails", () => { + const input = inspectWebNetworkCaptureInputSchema.parse({ + capture_path: "/selected/input.har", + format: "har", + }); + const cause = new OwnedCommandFailure( + "process", + "Observed command failure", + { + stdout: { text: "original provider output", bytes: 24 }, + stderr: { text: "private-marker provider reason", bytes: 30 }, + exitCode: 2, + signal: null, + }, + "Observed cleanup failure", + undefined, + ["pid:42"], + ); + const error = historicalCaptureFailure(input, cause, "decoder"); + expect(projectAnalysisError(error)).toMatchObject({ + code: "cleanup_incomplete", + details: { + diagnostics: { + previous_error: { + failure_kind: "process", + exit_code: 2, + stdout: "original provider output", + stderr: "private-marker provider reason", + }, + }, + }, + }); + const redacted = projectAnalysisError( + redactExplicitFailure(error, ["private-marker"]), + ); + expect(JSON.stringify(redacted)).not.toContain("private-marker"); + expect(redacted.code).toBe("cleanup_incomplete"); +}); +it.each(["io", "unavailable"] as const)( + "keeps %s read failures separate from malformed capture input", + (reason) => { + const input = inspectWebNetworkCaptureInputSchema.parse({ + capture_path: "/selected/input.har", + format: "har", + }); + const error = historicalCaptureFailure( + input, + new ArtifactReaderFailure(reason, "Observed acquisition failure"), + "capture-read", + ); + expect(projectAnalysisError(error)).toMatchObject({ + code: "execution_failure", + category: "execution_failure", + details: { diagnostics: { phase: "capture-read", failure_kind: reason } }, + }); + }, +); diff --git a/src/browser/history/CaptureFailures.ts b/src/browser/history/CaptureFailures.ts new file mode 100644 index 000000000..fec40d549 --- /dev/null +++ b/src/browser/history/CaptureFailures.ts @@ -0,0 +1,146 @@ +import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; +import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; +import { AnalysisError } from "../../domain/analysisErrorBase.js"; +import { + AnalysisAccessDeniedError, + AnalysisArtifactChangedError, + AnalysisCancelledError, + AnalysisCapabilityUnavailableError, + AnalysisInputError, + AnalysisOutputError, + AnalysisTimeoutError, +} from "../../domain/analysisErrorCore.js"; +import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; +import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; +import { + WEB_NETWORK_CAPTURE_LIMITS, + type InspectWebNetworkCaptureInput, +} from "../../domain/webNetworkCapture.js"; +import { OwnedCommandFailure } from "../../process/OwnedCommand.js"; +const OPERATION = "inspect_web_network_capture"; + +/** Preserve the origin and typed cause of a historical capture failure. */ +export const historicalCaptureFailure = ( + input: InspectWebNetworkCaptureInput, + cause: unknown, + phase: "capture-read" | "decoder", + options?: ExecutionOptions, +): AnalysisError => { + if (cause instanceof AnalysisError) return cause; + if (cause instanceof OwnedCommandFailure) { + if (cause.cleanupFailure !== null) + return new ProviderCleanupError( + input.format, + cause.resources, + { + reason: cause.cleanupFailure, + previous_error: { + failure_kind: cause.reason, + message: cause.message, + exit_code: cause.snapshot?.exitCode ?? null, + signal: cause.snapshot?.signal ?? null, + stdout: cause.snapshot?.stdout.text ?? null, + stderr: cause.snapshot?.stderr.text ?? null, + diagnostic_truncated: cause.snapshot?.diagnosticTruncated ?? null, + }, + }, + { operation: OPERATION, cause }, + ); + if (cause.reason === "cancelled") + return new AnalysisCancelledError(OPERATION); + if (cause.reason === "timeout") + return new AnalysisTimeoutError( + OPERATION, + WEB_NETWORK_CAPTURE_LIMITS.timeoutMs, + ); + if (cause.reason === "output-limit") + return new AnalysisOutputError(OPERATION, cause.message); + } + if (options?.signal?.aborted) return new AnalysisCancelledError(OPERATION); + if ( + phase === "capture-read" && + cause instanceof ArtifactReaderFailure && + cause.reason === "integrity" + ) + return new AnalysisArtifactChangedError( + OPERATION, + input.capture_path, + cause.message, + { cause }, + ); + if ( + phase === "capture-read" && + cause instanceof ArtifactReaderFailure && + cause.reason === "cancelled" + ) + return new AnalysisCancelledError(OPERATION); + if ( + phase === "capture-read" && + cause instanceof ArtifactReaderFailure && + (cause.reason === "limit" || + cause.reason === "format" || + cause.reason === "path") + ) + return new AnalysisInputError(OPERATION, { cause }, [ + { + path: ["capture_path"], + reason: cause.reason === "limit" ? "out_of_range" : "invalid_format", + message: + cause.reason === "limit" + ? `${cause.message}. Select a capture within the ${WEB_NETWORK_CAPTURE_LIMITS.inputBytes}-byte input budget.` + : cause.message, + ...(cause.reason === "limit" + ? { + expected: { + maximum_capture_bytes: WEB_NETWORK_CAPTURE_LIMITS.inputBytes, + }, + } + : {}), + }, + ]); + if ( + phase === "capture-read" && + cause instanceof Error && + "code" in cause && + (cause.code === "EACCES" || cause.code === "EPERM") + ) + return new AnalysisAccessDeniedError( + OPERATION, + input.capture_path, + cause.code, + { cause }, + ); + if ( + phase === "capture-read" && + cause instanceof Error && + "code" in cause && + ["ENOENT", "ENOTDIR"].includes(String(cause.code)) + ) + return new AnalysisInputError(OPERATION, { cause }, [ + { + path: ["capture_path"], + reason: "invalid_value", + message: `Selected capture could not be read (${String(cause.code)}): ${input.capture_path}.`, + }, + ]); + return new ProviderAdapterError(input.format, OPERATION, { + cause, + diagnostics: { + phase, + capture_path: input.capture_path, + reason: cause instanceof Error ? cause.message : String(cause), + ...(cause instanceof ArtifactReaderFailure + ? { failure_kind: cause.reason } + : {}), + ...(cause instanceof OwnedCommandFailure && cause.snapshot !== null + ? { + exit_code: cause.snapshot.exitCode ?? null, + signal: cause.snapshot.signal ?? null, + stdout: cause.snapshot.stdout.text, + stderr: cause.snapshot.stderr.text, + diagnostic_truncated: cause.snapshot.diagnosticTruncated ?? false, + } + : {}), + }, + }); +}; diff --git a/src/browser/history/CaptureFormatError.ts b/src/browser/history/CaptureFormatError.ts new file mode 100644 index 000000000..fd7a9272d --- /dev/null +++ b/src/browser/history/CaptureFormatError.ts @@ -0,0 +1,10 @@ +/** Expected malformed, unsupported or oversized producer data at a historical decoder boundary. */ +export class CaptureFormatError extends Error { + constructor( + readonly reason: "format" | "unsupported" | "input-limit", + message: string, + readonly pointer = "", + ) { + super(message); + } +} diff --git a/src/browser/history/CaptureRedaction.test.ts b/src/browser/history/CaptureRedaction.test.ts new file mode 100644 index 000000000..d6ce3bb1c --- /dev/null +++ b/src/browser/history/CaptureRedaction.test.ts @@ -0,0 +1,61 @@ +import { expect, it } from "vitest"; +import { CaptureRedaction } from "./CaptureRedaction.js"; + +it.each([ + { + value: " https://user:password@example.test/path ", + expected: " https://example.test/path ", + }, + { + value: "\tHTTPS://user:password@example.test/path\t", + expected: "\tHTTPS://example.test/path\t", + }, + { + value: " \t//user:password@example.test/path?token=ordinary#fragment\t ", + expected: " \t//example.test/path?token=ordinary#fragment\t ", + }, + { + value: "\r\n \v\fhttps://user:password@example.test/path", + expected: "\r\n \v\fhttps://example.test/path", + }, + { + value: "\u0000\u001f//user:password@example.test/path", + expected: "\u0000\u001f//example.test/path", + }, +])( + "excludes URL userinfo after optional whitespace while preserving the original header text: $value", + ({ value, expected }) => { + const redactor = new CaptureRedaction([]); + expect(redactor.text(value, "/response/headers/0/value", true)).toBe( + expected, + ); + expect(redactor.redactions).toEqual([ + { pointer: "/response/headers/0/value", reason: "transport-credential" }, + ]); + }, +); + +it("preserves the same URL-looking text in an ordinary extension", () => { + const redactor = new CaptureRedaction([]); + const value = " \thttps://user:password@example.test/path\t "; + expect(redactor.text(value, "/_extension")).toBe(value); + expect(redactor.redactions).toEqual([]); +}); + +it.each(["\ud800", "\udfff"])( + "does not invent replacement UTF-8 bytes for a non-scalar declaration", + (literal) => { + const redactor = new CaptureRedaction([literal]); + expect(redactor.sensitiveBytes(Buffer.from("\ufffd"))).toBe(false); + expect(redactor.text(literal, "/reported")).toBe(null); + }, +); + +it("matches scalar UTF-8 declarations and complete surrogate pairs exactly", () => { + expect( + new CaptureRedaction(["\ufffd"]).sensitiveBytes(Buffer.from("\ufffd")), + ).toBe(true); + expect( + new CaptureRedaction(["\ud83d\ude00"]).sensitiveBytes(Buffer.from("😀")), + ).toBe(true); +}); diff --git a/src/browser/history/CaptureRedaction.ts b/src/browser/history/CaptureRedaction.ts new file mode 100644 index 000000000..86945aed0 --- /dev/null +++ b/src/browser/history/CaptureRedaction.ts @@ -0,0 +1,93 @@ +import type { WebNetworkCaptureRecord } from "../../domain/webNetworkCapture.js"; + +type Redaction = WebNetworkCaptureRecord["redactions"][number]; +const CREDENTIAL_HEADERS = new Set([ + "authorization", + "proxy-authorization", + "cookie", + "set-cookie", +]); + +/** Literal redaction and structural authentication exclusions preserve unrelated local evidence. */ +export class CaptureRedaction { + readonly redactions: Redaction[] = []; + readonly #sensitiveByteValues: readonly Buffer[]; + constructor(readonly sensitiveValues: readonly string[]) { + // An unpaired surrogate has no UTF-8 representation. Buffer.from would + // invent replacement-character bytes and incorrectly discard real evidence. + this.#sensitiveByteValues = sensitiveValues.flatMap((value) => { + const encoded = Buffer.from(value, "utf8"); + return encoded.toString("utf8") === value ? [encoded] : []; + }); + } + + /** Preserve exact text unless explicitly sensitive or known transport URL userinfo. */ + text(value: string, pointer: string, transportUrl = false): string | null { + let result = value; + if (transportUrl) { + const match = + /^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?\/\/)([^/?#]*@)/i.exec(result); + if (match !== null) { + result = `${match[1]}${result.slice(match[0].length)}`; + this.redactions.push({ pointer, reason: "transport-credential" }); + } + } + if ( + this.sensitiveValues.some( + (literal) => value.includes(literal) || result.includes(literal), + ) + ) { + this.redactions.push({ pointer, reason: "explicit-sensitive-value" }); + return null; + } + return result; + } + + /** Remove a credential field's value without retaining its digest or encoded form. */ + credential(pointer: string): null { + this.redactions.push({ pointer, reason: "transport-credential" }); + return null; + } + + /** Known HAR credential surfaces; unknown extensions are ordinary caller-selected evidence. */ + harCredential( + pointer: string, + parent: Readonly>, + ): boolean { + return ( + (/^\/(request|response)\/headers\/\d+\/value$/.test(pointer) && + typeof parent.name === "string" && + CREDENTIAL_HEADERS.has(parent.name.toLowerCase())) || + /^\/(request|response)\/cookies\/\d+\/value$/.test(pointer) + ); + } + + /** Only actual request/redirect fields and known transport header URLs declare URL semantics. */ + harTransportUrl( + pointer: string, + parent?: Readonly>, + ): boolean { + return ( + pointer === "/request/url" || + pointer === "/response/redirectURL" || + (/^\/(request|response)\/headers\/\d+\/value$/.test(pointer) && + typeof parent?.name === "string" && + ["location", "referer", "origin"].includes(parent.name.toLowerCase())) + ); + } + + /** Preserve bytes unless an explicitly declared UTF-8 literal occurs in them. */ + sensitiveBytes(bytes: Buffer): boolean { + return this.#sensitiveByteValues.some((value) => bytes.includes(value)); + } +} + +/** Escape a producer property for an RFC 6901 location. */ +export const capturePointer = (parent: string, key: string): string => + `${parent}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`; + +/** Structural record guard does not duck-type number/byte sidecar markers. */ +export const captureObject = ( + value: unknown, +): value is Record => + typeof value === "object" && value !== null && !Array.isArray(value); diff --git a/src/browser/history/CaptureRelease.ts b/src/browser/history/CaptureRelease.ts new file mode 100644 index 000000000..4463befe9 --- /dev/null +++ b/src/browser/history/CaptureRelease.ts @@ -0,0 +1,13 @@ +/** Exact upstream package profile for unchanged offline HAR validation and numeric parsing. */ +export const HAR_CAPTURE_PROVIDER_IDENTITY = { + id: "har-schema/lossless-json", + name: "REA HAR capture adapter", + version: "har-schema@2.0.0;jsonc-parser@3.3.1;lossless-json@4.3.1", +} as const; + +/** Native executable profile verified with unchanged mitmproxy raw-state decoding. */ +export const MITMPROXY_CAPTURE_PROVIDER_IDENTITY = { + id: "mitmproxy-native-tnetstring", + name: "REA offline native mitmproxy adapter", + version: "12.2.3", +} as const; diff --git a/src/browser/history/HarCapture.test.ts b/src/browser/history/HarCapture.test.ts new file mode 100644 index 000000000..7e525ad5a --- /dev/null +++ b/src/browser/history/HarCapture.test.ts @@ -0,0 +1,546 @@ +import { createHash } from "node:crypto"; +import { expect, it } from "vitest"; +import { historicalHar } from "../../../tests/fixtures/historicalHar.js"; +import { decodeHarCapture } from "./HarCapture.js"; + +it.each([ + { values: [] }, + { values: ["response"] }, + { values: ["content"] }, + { values: ["log"] }, + { values: ["entries"] }, +])( + "validates canonical base64 even when its parent property is sensitive: %j", + ({ values }) => { + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + const input = { + log: { + ...fixture.log, + entries: [ + { + ...first, + response: { + ...first.response, + content: { + size: 1, + mimeType: "application/octet-stream", + encoding: "base64", + text: "AR==", + }, + }, + }, + ], + }, + }; + expect(() => decodeHarCapture(JSON.stringify(input), values)).toThrow( + "not valid canonical base64", + ); + }, +); + +it.each(["log", "entries", "ntries"])( + "excludes extracted record payloads beneath the marked structural property %s", + (literal) => { + const decoded = decodeHarCapture(JSON.stringify(historicalHar()), [ + literal, + ]); + expect(decoded.total_records).toBeGreaterThan(0); + expect(decoded.container.records_pointer).toBe(null); + for (const [ordinal, record] of decoded.records.entries()) { + expect(record).toMatchObject({ + ordinal, + location: { kind: "unknown", reason: "explicit-sensitive-value" }, + reported: null, + binary_fields: [], + numeric_literals: [], + redactions: [{ pointer: "", reason: "explicit-sensitive-value" }], + }); + } + }, +); + +it("excludes sensitive property identities before emitting keys and sidecar coordinates", () => { + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + const result = decodeHarCapture( + JSON.stringify({ + ...fixture, + _extension: { + "api-secret/~": { numeric: 123, text: "api-secret" }, + kept: 7, + }, + log: { ...fixture.log, entries: [{ ...first, "api-secret": 42 }] }, + }), + ["api-secret"], + ); + expect(JSON.stringify(result)).not.toContain("api-secret"); + expect(result.container.reported).toMatchObject({ _extension: { kept: 7 } }); + expect(result.container.redactions).toContainEqual({ + pointer: "/_extension", + reason: "explicit-sensitive-value", + scope: "property-name", + }); + expect(result.records[0]?.redactions).toContainEqual({ + pointer: "", + reason: "explicit-sensitive-value", + scope: "property-name", + }); +}); + +it.each([ + { value: "REDACTED", sensitive: ["REDACTED"] }, + { value: "literal[", sensitive: ["["] }, + { value: "before secret after", sensitive: ["secret", "REDACTED"] }, + { value: "before secret after", sensitive: ["secret", "[RED"] }, +])( + "excludes sensitive text without introducing replacement literals: $value", + ({ value, sensitive }) => { + const fixture = historicalHar(); + const result = decodeHarCapture( + JSON.stringify({ ...fixture, _sensitive: value, _ordinary: "unmarked" }), + sensitive, + ); + expect(result.container.reported).toMatchObject({ + _sensitive: null, + _ordinary: "unmarked", + }); + expect(result.container.redactions).toContainEqual({ + pointer: "/_sensitive", + reason: "explicit-sensitive-value", + }); + }, +); + +it("excludes a declared literal introduced by stripping transport URL userinfo", () => { + const fixture = historicalHar(); + const entry = fixture.log.entries[0]; + if (entry === undefined) throw new Error("fixture missing"); + const url = "https://example.test/sensitive"; + const input = { + log: { + ...fixture.log, + entries: [ + { + ...entry, + request: { + ...entry.request, + url: "https://user:password@example.test/sensitive", + }, + }, + ], + }, + }; + const result = decodeHarCapture(JSON.stringify(input), [url]); + expect(result.records[0]?.reported).toMatchObject({ request: { url: null } }); +}); + +it.each([ + '"_duplicate":1,"_duplicate":1', + '"_duplicate":null,"_duplicate":null', + '"_duplicate":{"value":1},"_duplicate":{"value":1}', + '"_duplicate":1,"\\u005fduplicate":1', +])( + "rejects equal-valued duplicate JSON members before projection: %s", + (members) => { + const text = JSON.stringify(historicalHar()).replace( + '"cache":{}', + `"cache":{},${members}`, + ); + expect(() => decodeHarCapture(text, [])).toThrow("duplicate"); + }, +); + +it("rejects identical duplicate core fields before schema validation", () => { + const text = JSON.stringify(historicalHar()).replace( + '"version":"1.2"', + '"version":"1.2","version":"1.2"', + ); + expect(() => decodeHarCapture(text, [])).toThrow("duplicate"); +}); + +it("reports an unsupported JSON schema boundary instead of dropping a producer property", () => { + const text = JSON.stringify(historicalHar()).replace( + '"cache":{}', + '"cache":{},"_extension":{"__proto__":{"preserved":7},"constructor":"ordinary"}', + ); + expect(() => decodeHarCapture(text, [])).toThrow( + "JSON schema boundary cannot preserve", + ); +}); + +it("preserves Unicode, offsets, sizes, duplicate URLs and opaque extensions without inventing bytes", () => { + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + fixture.log.entries.push(structuredClone(first)); + const result = decodeHarCapture( + JSON.stringify({ + ...fixture, + _extension: { isLosslessNumber: true, value: "ordinary" }, + }), + [], + ); + expect(result.total_records).toBe(2); + expect(result.records[0]?.reported).toEqual(fixture.log.entries[0]); + expect(result.records[0]?.binary_fields).toEqual([]); + expect(result.records[0]?.numeric_literals).toContainEqual({ + pointer: "/response/bodySize", + producer_type: "json-number", + literal: "99", + }); + expect(result.records[1]?.location).toEqual({ + kind: "json-pointer", + pointer: "/log/entries/1", + }); + expect(result.container.reported).toMatchObject({ + _extension: { isLosslessNumber: true, value: "ordinary" }, + log: { entries: null }, + }); +}); + +it.each(["9007199254740993", "4740993", "99"])( + "excludes explicitly sensitive numeric lexemes and their reported values: %s", + (literal) => { + const text = JSON.stringify(historicalHar()).replace( + '"cache":{}', + '"cache":{},"_big":9007199254740993', + ); + const result = decodeHarCapture(text, [literal]); + for (const record of result.records) { + expect( + record.numeric_literals.every( + (number) => !number.literal.includes(literal), + ), + ).toBe(true); + } + const pointer = literal === "99" ? "/response/bodySize" : "/_big"; + expect(result.records[0]?.redactions).toContainEqual({ + pointer, + reason: "explicit-sensitive-value", + }); + if (literal === "99") + expect(result.records[0]?.reported).toMatchObject({ + response: { bodySize: null }, + }); + else expect(result.records[0]?.reported).toMatchObject({ _big: null }); + }, +); + +it("keeps unsafe extension numeric lexemes and rejects unsafe mandatory schema numbers", () => { + const text = JSON.stringify(historicalHar()); + const result = decodeHarCapture( + text.replace('"cache":{}', '"cache":{},"_big":9007199254740993'), + [], + ); + expect(result.records[0]?.numeric_literals).toContainEqual({ + pointer: "/_big", + producer_type: "json-number", + literal: "9007199254740993", + }); + expect(result.records[0]?.reported).toMatchObject({ _big: null }); + expect(() => + decodeHarCapture( + text.replace('"bodySize":0', '"bodySize":9007199254740993'), + [], + ), + ).toThrow("HAR schema validation failed"); +}); + +it.each(["container", "record"] as const)( + "retains %s extension content without interpreting it as an HTTP body", + (location) => { + const fixture = historicalHar(); + const extension = { + response: { content: { encoding: "base64", text: "opaque extension" } }, + }; + const input = + location === "container" + ? { ...fixture, _extension: extension } + : { + log: { + ...fixture.log, + entries: fixture.log.entries.map((entry) => ({ + ...entry, + _extension: extension, + })), + }, + }; + const result = decodeHarCapture(JSON.stringify(input), []); + expect( + location === "container" + ? result.container.reported + : result.records[0]?.reported, + ).toMatchObject({ _extension: extension }); + expect(result.records[0]?.binary_fields).toEqual([]); + }, +); + +it("retains valid base64 bytes independently of declared sizes", () => { + const fixture = historicalHar(); + const entry = fixture.log.entries[0]; + if (entry === undefined) throw new Error("fixture missing"); + const text = JSON.stringify({ + log: { + ...fixture.log, + entries: [ + { + ...entry, + response: { + ...entry.response, + content: { + size: 999, + mimeType: "application/octet-stream", + encoding: "base64", + text: "AP9B\n", + }, + }, + }, + ], + }, + }); + const result = decodeHarCapture(text, []); + expect(result.records[0]?.binary_fields[0]).toMatchObject({ + pointer: "/response/content/text", + state: "retained", + content_base64: "AP9B", + bytes: 3, + }); + expect(result.records[0]?.reported).toMatchObject({ + response: { content: { size: 999, text: "AP9B\n" } }, + }); + expect(() => decodeHarCapture(text.replace("AP9B", "AR=="), [])).toThrow( + "not valid canonical base64", + ); +}); + +it("redacts actual authentication fields and explicit binary values while preserving similarly named extensions", () => { + const fixture = historicalHar(); + const entry = fixture.log.entries[0]; + if (entry === undefined) throw new Error("fixture missing"); + const raw = { + log: { + ...fixture.log, + entries: [ + { + ...entry, + request: { + ...entry.request, + url: "https://user:password@example.test/a?token=ordinary#fragment", + headers: [ + { name: "Authorization", value: "Bearer transport-secret" }, + { name: "X-Test", value: "a" }, + { name: "X-Test", value: "b" }, + ], + cookies: [{ name: "session", value: "cookie-secret" }], + }, + response: { + ...entry.response, + content: { + size: 5, + mimeType: "application/octet-stream", + encoding: "base64", + text: Buffer.from("chosen-private").toString("base64"), + }, + }, + _extension: { + response: { + headers: [{ name: "Authorization", value: "ordinary-extension" }], + }, + }, + }, + ], + }, + }; + const result = decodeHarCapture(JSON.stringify(raw), ["chosen-private"]); + const output = JSON.stringify(result); + for (const secret of [ + "password", + "transport-secret", + "cookie-secret", + Buffer.from("chosen-private").toString("base64"), + ]) + expect(output).not.toContain(secret); + expect(output).toContain("token=ordinary#fragment"); + expect(output).toContain("ordinary-extension"); + expect(result.records[0]?.reported).toMatchObject({ + request: { + headers: [ + { name: "Authorization", value: null }, + { name: "X-Test", value: "a" }, + { name: "X-Test", value: "b" }, + ], + }, + }); + expect(result.records[0]?.binary_fields[0]).toMatchObject({ + state: "redacted", + sha256: null, + content_base64: null, + }); +}); + +it.each([ + "{", + '{"log":{},"log":{}}', + JSON.stringify({ log: { version: "1.2", entries: [] } }), +])("rejects malformed producer data %s", (text) => { + expect(() => decodeHarCapture(text, [])).toThrow(); +}); + +it("preserves observed SaveHar null post-data without broadening arbitrary HAR producers", () => { + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + const log = { + ...fixture.log, + creator: { name: "mitmproxy", version: "12.2.3" }, + entries: [ + { + ...first, + request: { + ...first.request, + postData: { mimeType: "text/plain", text: null }, + }, + }, + ], + }; + expect( + decodeHarCapture(JSON.stringify({ log }), []).records[0]?.reported, + ).toMatchObject({ request: { postData: { text: null } } }); + expect(() => + decodeHarCapture( + JSON.stringify({ + log: { ...log, creator: { name: "other", version: "1" } }, + }), + [], + ), + ).toThrow("HAR schema validation failed"); +}); + +it.each([ + "c2VjcmV0", + "c2Vj", + createHash("sha256").update("secret").digest("hex"), +])( + "excludes sensitive derived byte representations from spaced HAR text: %s", + (literal) => { + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + const result = decodeHarCapture( + JSON.stringify({ + log: { + ...fixture.log, + entries: [ + { + ...first, + response: { + ...first.response, + content: { + size: 6, + mimeType: "application/octet-stream", + encoding: "base64", + text: "c2 VjcmV0", + }, + }, + }, + ], + }, + }), + [literal], + ); + expect(result.records[0]?.reported).toMatchObject({ + response: { content: { text: null } }, + }); + expect(result.records[0]?.binary_fields).toContainEqual({ + pointer: "/response/content/text", + representation: "har-base64-content", + state: "redacted", + content_base64: null, + bytes: null, + sha256: null, + }); + }, +); + +it("excludes a declared literal in encoded HAR text from its binary sidecar too", () => { + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + const log = { + ...fixture.log, + entries: [ + { + ...first, + response: { + ...first.response, + content: { + size: 3, + mimeType: "application/octet-stream", + encoding: "base64", + text: "AP9B", + }, + }, + }, + ], + }; + const result = decodeHarCapture(JSON.stringify({ log }), ["AP9B"]); + expect(JSON.stringify(result)).not.toContain("AP9B"); + expect(result.records[0]?.binary_fields[0]?.state).toBe("redacted"); +}); + +it("rejects excessive extension nesting instead of returning a partial capture", () => { + let extension: unknown = null; + for (let index = 0; index < 66; index++) extension = { child: extension }; + expect(() => + decodeHarCapture( + JSON.stringify({ ...historicalHar(), _extension: extension }), + [], + ), + ).toThrow("nesting budget"); +}); + +it.each([ + { name: "Location", side: "response" as const }, + { name: "Referer", side: "request" as const }, + { name: "Origin", side: "request" as const }, +])( + "excludes leading-OWS userinfo from a producer $name header", + ({ name, side }) => { + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + const value = + " \tHTTPS://user:password@example.test/path?token=ordinary#fragment\t "; + const result = decodeHarCapture( + JSON.stringify({ + log: { + ...fixture.log, + entries: [ + { + ...first, + [side]: { ...first[side], headers: [{ name, value }] }, + }, + ], + }, + }), + [], + ); + expect(result.records[0]?.reported).toMatchObject({ + [side]: { + headers: [ + { + name, + value: " \tHTTPS://example.test/path?token=ordinary#fragment\t ", + }, + ], + }, + }); + expect(result.records[0]?.redactions).toContainEqual({ + pointer: `/${side}/headers/0/value`, + reason: "transport-credential", + }); + }, +); diff --git a/src/browser/history/HarCapture.ts b/src/browser/history/HarCapture.ts new file mode 100644 index 000000000..ddc31567c --- /dev/null +++ b/src/browser/history/HarCapture.ts @@ -0,0 +1,244 @@ +import { createHash } from "node:crypto"; +import { isSafeNumber, LosslessNumber } from "lossless-json"; +import { parseHarJson } from "./HarJson.js"; +import type { JsonValue } from "../../domain/jsonValue.js"; +import { + WEB_NETWORK_CAPTURE_LIMITS, + type WebNetworkCapture, + type WebNetworkCaptureRecord, +} from "../../domain/webNetworkCapture.js"; +import { CaptureFormatError } from "./CaptureFormatError.js"; +import { + CaptureRedaction, + captureObject, + capturePointer, +} from "./CaptureRedaction.js"; +import { createHarValidator } from "./HarSchema.js"; +import { HAR_CAPTURE_PROVIDER_IDENTITY } from "./CaptureRelease.js"; + +type DecodedCapture = Omit< + WebNetworkCapture, + "artifact" | "format" | "runtime_attribution" | "limitations" +>; +type Projection = Pick< + WebNetworkCaptureRecord, + "reported" | "numeric_literals" | "binary_fields" | "redactions" +>; + +/** Decode a retained HAR with exact numeric lexemes and unchanged upstream schema validation. */ +export const decodeHarCapture = ( + text: string, + sensitiveValues: readonly string[], +): DecodedCapture => { + const raw = parseHarJson(text); + const validation = projectHar(raw, [], false); + createHarValidator()(validation.reported); + if ( + !captureObject(raw) || + !captureObject(raw.log) || + !Array.isArray(raw.log.entries) + ) + throw new CaptureFormatError("format", "HAR must contain log.entries."); + if (raw.log.version !== "1.2") + throw new CaptureFormatError( + "unsupported", + "Only the pinned HAR 1.2 schema profile is supported.", + "/log/version", + ); + const entries = raw.log.entries; + const recordsExcluded = ["log", "entries"].some((key) => + sensitiveValues.some((literal) => key.includes(literal)), + ); + const container = projectHar( + { ...raw, log: { ...raw.log, entries: null } }, + sensitiveValues, + true, + ); + const records = entries.map((entry, ordinal): WebNetworkCaptureRecord => { + // Validate each original record even when a structural ancestor excludes it. + const projected = projectHar(entry, sensitiveValues, true); + const projection: Projection = recordsExcluded + ? { + reported: null, + numeric_literals: [], + binary_fields: [], + redactions: [{ pointer: "", reason: "explicit-sensitive-value" }], + } + : projected; + return { + ordinal, + location: recordsExcluded + ? { kind: "unknown", reason: "explicit-sensitive-value" } + : { kind: "json-pointer", pointer: `/log/entries/${ordinal}` }, + ...projection, + limitations: [ + "HAR text is producer-decoded Unicode. Original body bytes remain unknown unless content explicitly uses valid base64; declared sizes are preserved independently of retained bytes. mitmproxy 12.2.3 SaveHar may report null optional postData.text; that null is retained and omitted only from the upstream validation copy.", + ...(recordsExcluded + ? [ + "This record's payload and sidecars are excluded because its structural ancestor property was explicitly marked sensitive.", + ] + : []), + ], + }; + }); + return { + decoder: { ...HAR_CAPTURE_PROVIDER_IDENTITY }, + container: { + reported: container.reported, + numeric_literals: container.numeric_literals, + redactions: container.redactions, + records_pointer: recordsExcluded ? null : "/log/entries", + }, + total_records: records.length, + records, + }; +}; + +const projectHar = ( + value: unknown, + sensitiveValues: readonly string[], + redact: boolean, +): Projection => { + const redactor = new CaptureRedaction(sensitiveValues); + const numeric_literals: Projection["numeric_literals"] = []; + const binary_fields: Projection["binary_fields"] = []; + const visit = ( + item: unknown, + pointer: string, + depth: number, + parent?: Readonly>, + ): JsonValue => { + if (depth > WEB_NETWORK_CAPTURE_LIMITS.depth) + throw new CaptureFormatError( + "input-limit", + "HAR exceeds the 64-level complete-evidence nesting budget.", + pointer, + ); + if ( + redact && + parent !== undefined && + redactor.harCredential(pointer, parent) + ) + return redactor.credential(pointer); + if (item instanceof LosslessNumber) { + if (redact && redactor.text(item.value, pointer) === null) return null; + numeric_literals.push({ + pointer, + producer_type: "json-number", + literal: item.value, + }); + return isSafeNumber(item.value) ? Number(item.value) : null; + } + if (typeof item === "string") { + if ( + redact && + pointer === "/response/content/text" && + parent?.encoding === "base64" + ) { + const bytes = decodeHarBase64(item, pointer); + const contentBase64 = bytes.toString("base64"); + const sha256 = createHash("sha256").update(bytes).digest("hex"); + const hidden = + redactor.sensitiveBytes(bytes) || + sensitiveValues.some( + (literal) => + item.includes(literal) || + contentBase64.includes(literal) || + sha256.includes(literal), + ); + binary_fields.push( + hidden + ? { + pointer, + representation: "har-base64-content", + state: "redacted", + content_base64: null, + bytes: null, + sha256: null, + } + : { + pointer, + representation: "har-base64-content", + state: "retained", + content_base64: contentBase64, + bytes: bytes.length, + sha256, + }, + ); + if (hidden) { + redactor.redactions.push({ + pointer, + reason: "explicit-sensitive-value", + }); + return null; + } + } + return redact + ? redactor.text( + item, + pointer, + redactor.harTransportUrl(pointer, parent), + ) + : item; + } + if (item === null || typeof item === "boolean") return item; + if (Array.isArray(item)) + return item.map((child, index) => + visit(child, capturePointer(pointer, String(index)), depth + 1), + ); + if (captureObject(item)) + return Object.fromEntries( + Object.entries(item).flatMap(([key, child]) => { + if ( + redact && + sensitiveValues.some((literal) => key.includes(literal)) + ) { + const beforeNumbers = numeric_literals.length; + const beforeBinaries = binary_fields.length; + const beforeRedactions = redactor.redactions.length; + visit(child, capturePointer(pointer, key), depth + 1, item); + numeric_literals.splice(beforeNumbers); + binary_fields.splice(beforeBinaries); + redactor.redactions.splice(beforeRedactions); + redactor.redactions.push({ + pointer, + reason: "explicit-sensitive-value", + scope: "property-name", + }); + return []; + } + return [ + [key, visit(child, capturePointer(pointer, key), depth + 1, item)], + ]; + }), + ); + throw new CaptureFormatError( + "format", + "HAR parser returned a non-JSON value.", + pointer, + ); + }; + return { + reported: visit(value, "", 0), + numeric_literals, + binary_fields, + redactions: redactor.redactions, + }; +}; + +const decodeHarBase64 = (value: string, pointer: string): Buffer => { + const compact = value.replace(/[\t\n\r ]/g, ""); + const bytes = Buffer.from(compact, "base64"); + if ( + !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test( + compact, + ) || + bytes.toString("base64") !== compact + ) + throw new CaptureFormatError( + "format", + "HAR content declares base64 but its text is not valid canonical base64 (ASCII whitespace is allowed).", + pointer, + ); + return bytes; +}; diff --git a/src/browser/history/HarCaptureAdapter.ts b/src/browser/history/HarCaptureAdapter.ts new file mode 100644 index 000000000..b66167a5f --- /dev/null +++ b/src/browser/history/HarCaptureAdapter.ts @@ -0,0 +1,24 @@ +import { fileURLToPath } from "node:url"; +import type { HistoricalCaptureFormatAdapter } from "./HistoricalCaptureFormatAdapter.js"; +import { HAR_CAPTURE_PROVIDER_IDENTITY } from "./CaptureRelease.js"; + +/** HAR's parser/schema packages run in one bounded Node heap, without network or target execution. */ +export class HarCaptureAdapter implements HistoricalCaptureFormatAdapter { + readonly format = "har"; + readonly identity = HAR_CAPTURE_PROVIDER_IDENTITY; + async command( + requestPath: string, + ): Promise<{ command: string; arguments: readonly string[] }> { + return { + command: process.execPath, + arguments: [ + "--max-old-space-size=192", + "--max-semi-space-size=8", + "--v8-pool-size=1", + "--single-threaded", + fileURLToPath(new URL("./HarCaptureProcess.js", import.meta.url)), + requestPath, + ], + }; + } +} diff --git a/src/browser/history/HarCaptureProcess.ts b/src/browser/history/HarCaptureProcess.ts new file mode 100644 index 000000000..0751d0b80 --- /dev/null +++ b/src/browser/history/HarCaptureProcess.ts @@ -0,0 +1,64 @@ +import { readFile, writeFile } from "node:fs/promises"; +import { TextDecoder } from "node:util"; +import { z } from "zod"; +import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; +import { WEB_NETWORK_CAPTURE_LIMITS } from "../../domain/webNetworkCapture.js"; +import { CaptureFormatError } from "./CaptureFormatError.js"; +import { decodeHarCapture } from "./HarCapture.js"; + +const requestSchema = z.strictObject({ + snapshot_path: z.string(), + reply_path: z.string(), + sensitive_values: z.array(z.string().min(1)), +}); + +/** Decode untrusted HAR in an owned, bounded heap rather than the long-lived MCP process. */ +const main = async (): Promise => { + const request = requestSchema.parse( + JSON.parse(await readFile(process.argv[2] ?? "", "utf8")), + ); + let reply: unknown; + try { + const snapshot = await readStableArtifact( + request.snapshot_path, + WEB_NETWORK_CAPTURE_LIMITS.inputBytes, + ); + let text: string; + try { + text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode( + snapshot.bytes, + ); + } catch { + throw new CaptureFormatError("format", "HAR is not valid UTF-8 JSON."); + } + reply = { + ok: true, + value: decodeHarCapture(text, request.sensitive_values), + }; + } catch (cause: unknown) { + reply = + cause instanceof CaptureFormatError + ? { + ok: false, + reason: cause.reason, + message: cause.message, + pointer: cause.pointer, + } + : { + ok: false, + reason: "decoder", + message: `HAR decoder failed unexpectedly (${cause instanceof Error ? cause.name : "unknown exception"}).`, + pointer: "", + }; + } + let text = JSON.stringify(reply); + if (Buffer.byteLength(text) > WEB_NETWORK_CAPTURE_LIMITS.outputBytes) + text = JSON.stringify({ + ok: false, + reason: "limit", + message: "HAR reply exceeds the complete-evidence output budget.", + pointer: "", + }); + await writeFile(request.reply_path, text, { flag: "wx", mode: 0o600 }); +}; +await main(); diff --git a/src/browser/history/HarJson.ts b/src/browser/history/HarJson.ts new file mode 100644 index 000000000..93f25e7cd --- /dev/null +++ b/src/browser/history/HarJson.ts @@ -0,0 +1,118 @@ +import { visit } from "jsonc-parser"; +import { LosslessNumber } from "lossless-json"; +import { WEB_NETWORK_CAPTURE_LIMITS } from "../../domain/webNetworkCapture.js"; +import { CaptureFormatError } from "./CaptureFormatError.js"; + +type Frame = + | { + kind: "object"; + pointer: string; + members: Map; + pendingKey?: string; + } + | { kind: "array"; pointer: string; items: unknown[] }; + +/** Materialize upstream JSON events without duplicate folding, prototype setters or numeric rounding. */ +export const parseHarJson = (text: string): unknown => { + const frames: Frame[] = []; + let result: unknown; + const valuePointer = (): string => { + const parent = frames.at(-1); + if (parent === undefined) return ""; + if (parent.kind === "array") + return `${parent.pointer}/${parent.items.length}`; + return parent.pendingKey === undefined + ? parent.pointer + : `${parent.pointer}/${parent.pendingKey.replaceAll("~", "~0").replaceAll("/", "~1")}`; + }; + const checkDepth = (): void => { + if (frames.length > WEB_NETWORK_CAPTURE_LIMITS.depth) + throw new CaptureFormatError( + "input-limit", + "HAR exceeds the 64-level complete-evidence nesting budget.", + valuePointer(), + ); + }; + const append = (value: unknown): void => { + const parent = frames.at(-1); + if (parent === undefined) result = value; + else if (parent.kind === "array") parent.items.push(value); + else { + if (parent.pendingKey === undefined) + throw new TypeError( + "JSON visitor omitted an object property identity.", + ); + parent.members.set(parent.pendingKey, value); + delete parent.pendingKey; + } + }; + visit( + text, + { + onObjectBegin: () => { + checkDepth(); + frames.push({ + kind: "object", + pointer: valuePointer(), + members: new Map(), + }); + }, + onObjectProperty: (key) => { + if (key === "__proto__") + throw new CaptureFormatError( + "unsupported", + "HAR object contains a __proto__ member that the current JSON schema boundary cannot preserve.", + ); + const frame = frames.at(-1); + if (frame?.kind !== "object") + throw new TypeError( + "JSON visitor emitted a property outside an object.", + ); + if (frame.members.has(key)) + throw new CaptureFormatError( + "format", + "HAR JSON contains duplicate object members.", + ); + frame.pendingKey = key; + }, + onObjectEnd: () => { + const frame = frames.pop(); + if (frame?.kind !== "object") + throw new TypeError("JSON visitor ended an unexpected object."); + append(Object.fromEntries(frame.members)); + }, + onArrayBegin: () => { + checkDepth(); + frames.push({ kind: "array", pointer: valuePointer(), items: [] }); + }, + onArrayEnd: () => { + const frame = frames.pop(); + if (frame?.kind !== "array") + throw new TypeError("JSON visitor ended an unexpected array."); + append(frame.items); + }, + onLiteralValue: (value: unknown, offset, length) => { + checkDepth(); + if (typeof value === "number") + append(new LosslessNumber(text.slice(offset, offset + length))); + else if ( + value === null || + typeof value === "boolean" || + typeof value === "string" + ) + append(value); + else + throw new TypeError("JSON visitor emitted an unsupported literal."); + }, + onError: () => { + throw new CaptureFormatError("format", "HAR is malformed JSON."); + }, + }, + { + disallowComments: true, + allowTrailingComma: false, + allowEmptyContent: false, + }, + ); + return result; +}; diff --git a/src/browser/history/HarSchema.ts b/src/browser/history/HarSchema.ts new file mode 100644 index 000000000..069377f8a --- /dev/null +++ b/src/browser/history/HarSchema.ts @@ -0,0 +1,87 @@ +import { createRequire } from "node:module"; +import { Ajv } from "ajv"; +import { z } from "zod"; +import { CaptureFormatError } from "./CaptureFormatError.js"; + +/** Validate using the unchanged, pinned upstream HAR 1.2 draft-06 schemas. */ +export const createHarValidator = (): ((value: unknown) => void) => { + const require = createRequire(import.meta.url); + const schemas = z + .record(z.string(), z.record(z.string(), z.unknown())) + .parse(require("har-schema")); + const ajv = new Ajv({ strict: false, allErrors: false, ownProperties: true }); + ajv.addMetaSchema(require("ajv/dist/refs/json-schema-draft-06.json")); + const plugin: unknown = require("ajv-formats"); + if (typeof plugin !== "function") + throw new Error("Pinned AJV formats plugin is unavailable"); + plugin(ajv); + for (const schema of Object.values(schemas)) ajv.addSchema(schema); + const validate = ajv.getSchema("har.json#"); + if (validate === undefined) + throw new Error("Pinned HAR root schema is unavailable"); + return (value) => { + allowMitmproxyAbsentPostData(value); + if (!validate(value)) { + const error = validate.errors?.[0]; + throw new CaptureFormatError( + "format", + `HAR schema validation failed: ${error?.keyword ?? "unknown constraint"}.`, + error?.instancePath ?? "", + ); + } + }; +}; + +/** Observed SaveHar 12.2.3 compatibility: validate omitted optional text while retaining reported null. */ +const allowMitmproxyAbsentPostData = (value: unknown): void => { + const parsed = z + .object({ + log: z.object({ + creator: z.object({ + name: z.literal("mitmproxy"), + version: z.literal("12.2.3"), + }), + entries: z.array(z.unknown()), + }), + }) + .safeParse(value); + if ( + !parsed.success || + typeof value !== "object" || + value === null || + !("log" in value) + ) + return; + const log = value.log; + if ( + typeof log !== "object" || + log === null || + !("entries" in log) || + !Array.isArray(log.entries) + ) + return; + for (const entry of log.entries) { + const candidate: unknown = entry; + if ( + typeof candidate !== "object" || + candidate === null || + !("request" in candidate) + ) + continue; + const request = candidate.request; + if ( + typeof request !== "object" || + request === null || + !("postData" in request) + ) + continue; + const body = request.postData; + if ( + typeof body === "object" && + body !== null && + "text" in body && + body.text === null + ) + delete body.text; + } +}; diff --git a/src/browser/history/HistoricalCaptureDecoder.ts b/src/browser/history/HistoricalCaptureDecoder.ts new file mode 100644 index 000000000..e180ad6ad --- /dev/null +++ b/src/browser/history/HistoricalCaptureDecoder.ts @@ -0,0 +1,250 @@ +import { historicalCaptureFailure } from "./CaptureFailures.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; +import type { HistoricalCaptureFormatAdapter } from "./HistoricalCaptureFormatAdapter.js"; +import { randomUUID } from "node:crypto"; +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { z } from "zod"; +import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; +import { readStableArtifact } from "../../artifacts/readStableArtifact.js"; +import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; +import type { AnalysisError } from "../../domain/analysisErrorBase.js"; +import { + AnalysisCapabilityUnavailableError, + AnalysisCancelledError, + AnalysisInputError, + AnalysisOutputError, +} from "../../domain/analysisErrorCore.js"; +import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; +import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; +import { err, ok, type Result } from "../../domain/result.js"; +import { + WEB_NETWORK_CAPTURE_LIMITS, + webNetworkCaptureSchema, + type InspectWebNetworkCaptureInput, + type WebNetworkCapture, +} from "../../domain/webNetworkCapture.js"; +import { runOwnedCommand } from "../../process/OwnedCommand.js"; +import { PrivateRuntimeRoot } from "../../process/PrivateRuntimeRoot.js"; +import { redactExplicitFailure } from "../../domain/explicitSensitiveFailure.js"; + +const OPERATION = "inspect_web_network_capture"; +const decodedSchema = webNetworkCaptureSchema.omit({ + artifact: true, + format: true, + runtime_attribution: true, + limitations: true, +}); +const replySchema = z.discriminatedUnion("ok", [ + z.strictObject({ ok: z.literal(true), value: decodedSchema }), + z.strictObject({ + ok: z.literal(false), + reason: z.enum([ + "format", + "unsupported", + "input-limit", + "resource-limit", + "decoder", + "limit", + ]), + message: z.string(), + pointer: z.string(), + }), +]); + +/** One owned snapshot/process/root lifecycle for replaceable historical format decoders. */ +export class HistoricalCaptureDecoder { + constructor( + readonly adapters: readonly HistoricalCaptureFormatAdapter[], + readonly environment: Readonly, + readonly createRuntime: () => Promise< + Pick + > = () => PrivateRuntimeRoot.create({ prefix: "rea-web-capture-" }), + ) {} + + /** Snapshot the selected artifact, decode it offline, and release private credentials and processes. */ + async inspect( + input: InspectWebNetworkCaptureInput, + options?: ExecutionOptions, + ): Promise> { + let runtime: Pick | undefined; + let result: Result; + let phase: "capture-read" | "decoder" = "capture-read"; + try { + const adapter = this.adapters.find( + (candidate) => candidate.format === input.format, + ); + if (adapter === undefined) + throw new AnalysisCapabilityUnavailableError( + input.format, + OPERATION, + "No adapter is configured for the selected capture format.", + ); + const snapshot = await readStableArtifact( + input.capture_path, + WEB_NETWORK_CAPTURE_LIMITS.inputBytes, + options?.signal, + ); + phase = "decoder"; + runtime = await this.createRuntime(); + const snapshotPath = join(runtime.path, "capture.snapshot"); + const requestPath = join(runtime.path, "request.json"); + const replyPath = join(runtime.path, "reply.json"); + await writeFile(snapshotPath, snapshot.bytes, { + mode: 0o600, + flag: "wx", + ...(options?.signal === undefined ? {} : { signal: options.signal }), + }); + await writeFile( + requestPath, + JSON.stringify({ + snapshot_path: snapshotPath, + reply_path: replyPath, + sensitive_values: input.sensitive_values, + }), + { mode: 0o600, flag: "wx" }, + ); + const command = await adapter.command(requestPath, runtime.path); + const environment: NodeJS.ProcessEnv = { + ...this.environment, + UV_THREADPOOL_SIZE: "1", + }; + delete environment.NODE_OPTIONS; + await runOwnedCommand( + { + ...command, + runId: `rea-web-capture-${randomUUID()}`, + cwd: runtime.path, + hostEnvironment: environment, + }, + { + timeoutMs: WEB_NETWORK_CAPTURE_LIMITS.timeoutMs, + diagnosticBytes: 1024 * 1024, + }, + options?.signal === undefined ? {} : { signal: options.signal }, + ); + let reply: z.output; + try { + const replyFile = await readStableArtifact( + replyPath, + WEB_NETWORK_CAPTURE_LIMITS.outputBytes, + options?.signal, + ); + reply = replySchema.parse(JSON.parse(replyFile.bytes.toString("utf8"))); + } catch (cause: unknown) { + if (options?.signal?.aborted) + throw new AnalysisCancelledError(OPERATION); + throw new AnalysisOutputError( + OPERATION, + `Owned capture decoder reply failed for ${input.capture_path}: ${replyFailureReason(cause)}`, + { cause }, + ); + } + if (!reply.ok) { + if (reply.reason === "format") + throw new AnalysisInputError(OPERATION, undefined, [ + { + path: ["capture_path", reply.pointer], + reason: "invalid_format", + message: reply.message, + }, + ]); + if (reply.reason === "input-limit") + throw new AnalysisInputError(OPERATION, undefined, [ + { + path: ["capture_path", reply.pointer], + reason: "out_of_range", + message: reply.message, + expected: { + maximum_capture_nesting: WEB_NETWORK_CAPTURE_LIMITS.depth, + }, + }, + ]); + if (reply.reason === "decoder" || reply.reason === "resource-limit") + throw new ProviderAdapterError(input.format, OPERATION, { + diagnostics: { + phase: "decoder", + failure_kind: reply.reason, + reason: reply.message, + pointer: reply.pointer, + }, + }); + if (reply.reason === "unsupported") + throw new AnalysisCapabilityUnavailableError( + input.format, + OPERATION, + reply.message, + ); + throw new AnalysisOutputError(OPERATION, reply.message); + } + if ( + reply.value.decoder.id !== adapter.identity.id || + reply.value.decoder.name !== adapter.identity.name || + reply.value.decoder.version !== adapter.identity.version + ) + throw new AnalysisOutputError( + OPERATION, + "Capture decoder changed the selected upstream profile identity.", + ); + result = ok({ + ...reply.value, + artifact: { + path: input.capture_path, + sha256: snapshot.sha256, + bytes: snapshot.bytes.length, + }, + format: input.format, + runtime_attribution: "unknown", + limitations: [ + "This is retained producer evidence, without live browser transaction IDs, scenario provenance, execution attribution or deployment authenticity. Recorded URLs are never fetched.", + "Every record is decoded before selection; no partial success is returned on malformed input or a resource limit.", + "Known authentication headers/cookies and explicitly marked literal values are excluded. Unknown extension names do not establish sensitivity.", + "A 32 MiB input, 96 MiB reply and 64-level nesting budget bound complete evidence. HAR uses a 192 MiB Node old-generation heap; native decoding uses a 768 MiB Linux address-space limit. Each owned command has a 30-second deadline with independent cleanup.", + ], + }); + } catch (cause: unknown) { + result = err( + redactExplicitFailure( + historicalCaptureFailure(input, cause, phase, options), + input.sensitive_values, + ), + ); + } + if (runtime !== undefined) { + try { + await runtime.close(); + } catch (cause: unknown) { + return err( + redactExplicitFailure( + new ProviderCleanupError( + input.format, + [runtime.path], + { + capture_path: input.capture_path, + previous_error: result.ok + ? null + : projectAnalysisError(result.error), + reason: cause instanceof Error ? cause.message : String(cause), + }, + { operation: OPERATION }, + ), + input.sensitive_values, + ), + ); + } + } + return result.ok && options?.signal?.aborted + ? err(new AnalysisCancelledError(OPERATION)) + : result; + } +} + +const replyFailureReason = (cause: unknown): string => { + if (cause instanceof ArtifactReaderFailure) return cause.message; + if (cause instanceof z.ZodError) + return `Reply violates its schema at ${cause.issues[0]?.path.map(String).join(".") ?? "root"}.`; + if (cause instanceof SyntaxError) return "Reply is malformed JSON."; + if (cause instanceof Error && "code" in cause) + return `Reply file could not be read (${String(cause.code)}).`; + return "Reply could not be validated."; +}; diff --git a/src/browser/history/HistoricalCaptureFormatAdapter.ts b/src/browser/history/HistoricalCaptureFormatAdapter.ts new file mode 100644 index 000000000..55281b5ec --- /dev/null +++ b/src/browser/history/HistoricalCaptureFormatAdapter.ts @@ -0,0 +1,15 @@ +import type { InspectWebNetworkCaptureInput } from "../../domain/webNetworkCapture.js"; + +/** Format-specific process profile keeps mature upstream code behind a replaceable adapter seam. */ +export interface HistoricalCaptureFormatAdapter { + readonly format: InspectWebNetworkCaptureInput["format"]; + readonly identity: { + readonly id: string; + readonly name: string; + readonly version: string; + }; + command( + requestPath: string, + runtimePath: string, + ): Promise<{ command: string; arguments: readonly string[] }>; +} diff --git a/src/browser/history/MitmproxyCaptureAdapter.ts b/src/browser/history/MitmproxyCaptureAdapter.ts new file mode 100644 index 000000000..a3ebd66f7 --- /dev/null +++ b/src/browser/history/MitmproxyCaptureAdapter.ts @@ -0,0 +1,56 @@ +import { access } from "node:fs/promises"; +import { constants } from "node:fs"; +import { isAbsolute } from "node:path"; +import { fileURLToPath } from "node:url"; +import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js"; +import type { HistoricalCaptureFormatAdapter } from "./HistoricalCaptureFormatAdapter.js"; +import { MITMPROXY_CAPTURE_PROVIDER_IDENTITY } from "./CaptureRelease.js"; +const OPERATION = "inspect_web_network_capture"; + +/** Native mitmproxy decoding is offline and uses only an explicitly configured Linux tool. */ +export class MitmproxyCaptureAdapter implements HistoricalCaptureFormatAdapter { + readonly format = "mitmproxy"; + readonly identity = MITMPROXY_CAPTURE_PROVIDER_IDENTITY; + constructor(readonly environment: Readonly) {} + async command( + requestPath: string, + runtimePath: string, + ): Promise<{ command: string; arguments: readonly string[] }> { + const command = this.environment.REA_MITMDUMP_COMMAND; + if ( + process.platform !== "linux" || + command === undefined || + !isAbsolute(command) + ) + throw new AnalysisCapabilityUnavailableError( + "mitmproxy", + OPERATION, + "Native capture decoding requires Linux and an absolute REA_MITMDUMP_COMMAND path to caller-supplied mitmdump 12.2.3; HAR needs no external engine.", + ); + try { + await access(command, constants.R_OK | constants.X_OK); + } catch (cause: unknown) { + throw new AnalysisCapabilityUnavailableError( + "mitmproxy", + OPERATION, + `Configured mitmdump executable is unavailable: ${command}.`, + { cause }, + ); + } + return { + command, + arguments: [ + "--no-server", + "-q", + "--set", + `confdir=${runtimePath}`, + "--set", + `rea_request_path=${requestPath}`, + "-s", + fileURLToPath( + new URL("../../../bridge/mitmproxy/capture.py", import.meta.url), + ), + ], + }; + } +} diff --git a/src/cli.ts b/src/cli.ts index 8ce590f05..10f1a0949 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -16,6 +16,7 @@ import { registerWebScriptCommands } from "./cli/webScriptCommands.js"; import { registerWebModuleCommands } from "./cli/webModuleCommands.js"; import { registerWebSourceLocationCommands } from "./cli/webSourceLocationCommands.js"; import { registerWebRuntimeCommands } from "./cli/webRuntimeCommands.js"; +import { registerWebNetworkCaptureCommands } from "./cli/webNetworkCaptureCommands.js"; import { registerJavaScriptRecoveryCommands } from "./cli/javascriptRecoveryCommands.js"; import { registerAdvancedBrowserCommands } from "./cli/browserAdvancedCommands.js"; import { registerBrowserScenarioCommands } from "./cliBrowserScenarioCommands.js"; @@ -23,6 +24,7 @@ import { registerElectronCommands } from "./cli/electronCommands.js"; import { registerJavaScriptRuntimeObservationCommands } from "./cliJavaScriptRuntimeCommands.js"; import { registerApplicationCommands } from "./cli/applicationCommands.js"; import type { CliInstance } from "./cli/types.js"; +import type { CliResultOutput } from "./cli/streamedJsonOutput.js"; /** * Build the one-shot Incur CLI without starting Hopper at import time. @@ -31,6 +33,7 @@ import type { CliInstance } from "./cli/types.js"; */ export const createCli = ( environment: Readonly> = process.env, + resultOutput?: CliResultOutput, ): CliInstance => { const logger = createLogger( "cli", @@ -50,7 +53,7 @@ export const createCli = ( }); registerSetupCommands(cli, logger); - registerCoreAnalysisCommands(cli, logger); + registerCoreAnalysisCommands(cli, logger, resultOutput); registerUtilityCommands(cli, logger, environment); registerArtifactCommands(cli, logger); registerManagedCommands(cli, logger); @@ -63,10 +66,11 @@ export const createCli = ( registerWebModuleCommands(cli, logger, environment); registerWebSourceLocationCommands(cli, logger, environment); registerWebRuntimeCommands(cli, logger); + registerWebNetworkCaptureCommands(cli, logger, environment); registerJavaScriptRecoveryCommands(cli, logger, environment); registerAdvancedBrowserCommands(cli, logger); registerBrowserScenarioCommands(cli, logger); - registerElectronCommands(cli, logger); + registerElectronCommands(cli, logger, resultOutput); registerJavaScriptRuntimeObservationCommands(cli, logger); registerApplicationCommands(cli, logger); return cli; diff --git a/src/cli/artifactCommands.ts b/src/cli/artifactCommands.ts index 4681608e0..6dc93f7ec 100644 --- a/src/cli/artifactCommands.ts +++ b/src/cli/artifactCommands.ts @@ -1,7 +1,7 @@ import { z } from "incur"; import { runProviderAnalysis } from "../composition/directAnalysis.js"; -import { createArtifactExtractionDestination } from "../application/ArtifactExtractionDestination.js"; +import { createArtifactExtractionDestination } from "../application/artifacts/ArtifactExtractionDestination.js"; import { logCliCommand } from "../cliLogging.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; import type { Logger } from "../logger.js"; diff --git a/src/cli/browserAdvancedCommands.ts b/src/cli/browserAdvancedCommands.ts index 2d299503f..0fd49960e 100644 --- a/src/cli/browserAdvancedCommands.ts +++ b/src/cli/browserAdvancedCommands.ts @@ -10,7 +10,10 @@ import { } from "../application/BrowserObservationService.js"; import { CdpBrowserProvider } from "../browser/CdpBrowserProvider.js"; import { logCliCommand } from "../cliLogging.js"; -import { AnalysisInputError } from "../domain/analysisErrorCore.js"; +import { + AnalysisInputError, + type AnalysisInputIssue, +} from "../domain/analysisErrorCore.js"; import { browserCaptureComparisonInputSchema } from "../domain/browserCaptureComparison.js"; import { discoverWebMcpToolsInputSchema } from "../domain/webMcpDiscovery.js"; import { @@ -89,19 +92,32 @@ const registerCaptureDiff = ( }), run: ({ args, options }) => logCliCommand(logger, "compare-web-captures", async () => { - const before = parseJson(args.beforeJson); - const after = parseJson(args.afterJson); + const before = safeParseJson(args.beforeJson); + const after = safeParseJson(args.afterJson); + const normalization = safeParseJson(options.normalizationJson); + if (!before.ok) + return inputError("compare_web_captures", [ + invalidJsonIssue("before"), + ]); + if (!after.ok) + return inputError("compare_web_captures", [ + invalidJsonIssue("after"), + ]); + if (!normalization.ok) + return inputError("compare_web_captures", [ + invalidJsonIssue("normalization"), + ]); const scenarioComparison = browserCaptureComparisonInputSchema.safeParse({ - before_scenario: before, - after_scenario: after, - normalization: parseJson(options.normalizationJson), + before_scenario: before.value, + after_scenario: after.value, + normalization: normalization.value, }); const parsed = scenarioComparison.success ? scenarioComparison : browserCaptureComparisonInputSchema.safeParse({ - before, - after, + before: before.value, + after: after.value, }); if (!parsed.success) return inputError("compare_web_captures"); const result = await compareWebCaptureEvidence( @@ -186,5 +202,14 @@ const parseJson = (value: string): unknown => { return parsed.ok ? parsed.value : undefined; }; -const inputError = (operation: string): JsonValue => - browserCliError(new AnalysisInputError(operation)); +const invalidJsonIssue = (field: string): AnalysisInputIssue => ({ + path: [field], + reason: "invalid_format", + expected: "JSON", +}); + +const inputError = ( + operation: string, + issues: readonly AnalysisInputIssue[] = [], +): JsonValue => + browserCliError(new AnalysisInputError(operation, undefined, issues)); diff --git a/src/cli/coreAnalysisCommands.ts b/src/cli/coreAnalysisCommands.ts index da50ca830..e323348da 100644 --- a/src/cli/coreAnalysisCommands.ts +++ b/src/cli/coreAnalysisCommands.ts @@ -2,12 +2,14 @@ import type { Logger } from "../logger.js"; import { registerCoreBinaryCommands } from "./coreBinaryCommands.js"; import { registerCoreNativeCommands } from "./coreNativeCommands.js"; import type { CliInstance } from "./types.js"; +import type { CliResultOutput } from "./streamedJsonOutput.js"; /** Register core binary and native deep-analysis CLI commands. */ export const registerCoreAnalysisCommands = ( cli: CliInstance, logger: Logger, + resultOutput?: CliResultOutput, ): void => { - registerCoreBinaryCommands(cli, logger); + registerCoreBinaryCommands(cli, logger, resultOutput); registerCoreNativeCommands(cli, logger); }; diff --git a/src/cli/coreBinaryCommands.ts b/src/cli/coreBinaryCommands.ts index 6c70a2626..441d0e147 100644 --- a/src/cli/coreBinaryCommands.ts +++ b/src/cli/coreBinaryCommands.ts @@ -13,13 +13,18 @@ import { } from "./options.js"; import type { CliInstance } from "./types.js"; import { runCliJavaScriptApplicationAnalysis } from "./javascriptApplicationAnalysis.js"; +import type { + CliCommandOutput, + CliResultOutput, +} from "./streamedJsonOutput.js"; /** Register provider-neutral binary overview and procedure CLI commands. */ export const registerCoreBinaryCommands = ( cli: CliInstance, logger: Logger, + resultOutput?: CliResultOutput, ): void => { - registerOverviewCommands(cli, logger); + registerOverviewCommands(cli, logger, resultOutput); registerFunctionCommand(cli, logger); registerInstructionsCommand(cli, logger); registerSearchCommand(cli, logger); @@ -27,7 +32,11 @@ export const registerCoreBinaryCommands = ( registerTraceCommand(cli, logger); }; -const registerOverviewCommands = (cli: CliInstance, logger: Logger): void => { +const registerOverviewCommands = ( + cli: CliInstance, + logger: Logger, + resultOutput?: CliResultOutput, +): void => { const overviewOptions = z.object({ snapshot: z .string() @@ -43,9 +52,16 @@ const registerOverviewCommands = (cli: CliInstance, logger: Logger): void => { path: z.string().describe("App, program, or analysis database path"), }), options: overviewOptions, - run: ({ args, options }) => + run: ({ args, options, format }) => logCliCommand(logger, "analyze", () => - runRoutedOverview(args.path, options, logger), + runRoutedOverview( + args.path, + options, + logger, + resultOutput === undefined + ? undefined + : { output: resultOutput, command: CLI_COMMANDS.analyze, format }, + ), ), }); cli.command(CLI_COMMANDS.inspect, { @@ -109,6 +125,7 @@ const runRoutedOverview = async ( readonly provider?: string | undefined; }, logger: Logger, + output?: CliCommandOutput, ) => { if ( options.provider === undefined && @@ -116,9 +133,10 @@ const runRoutedOverview = async ( options["target-format"] === undefined && (await isJavaScriptApplicationPath(path)) ) - return runCliJavaScriptApplicationAnalysis({ - input_path: resolve(path), - }); + return runCliJavaScriptApplicationAnalysis( + { input_path: resolve(path) }, + output, + ); return runDirectAnalysis( path, "binary_overview", diff --git a/src/cli/electronCommands.ts b/src/cli/electronCommands.ts index f89b1e518..e747dc5c9 100644 --- a/src/cli/electronCommands.ts +++ b/src/cli/electronCommands.ts @@ -14,24 +14,27 @@ import { import { electronActiveObservationInputSchema } from "../domain/javascript/electronActiveObservation.js"; import { AnalysisInputError } from "../domain/analysisErrorCore.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; +import { projectInputIssues } from "../domain/inputIssueProjection.js"; import type { JsonValue } from "../domain/jsonValue.js"; import type { Logger } from "../logger.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; -import { parseCliJsonInput } from "../cliJsonInput.js"; +import { parseCliJsonInput, resolveCliJsonPaths } from "../cliJsonInput.js"; import { electronPageInspectionOptions, javascriptApplicationOptions, } from "../cliObservationOptions.js"; import { runCliJavaScriptApplicationAnalysis } from "./javascriptApplicationAnalysis.js"; +import type { CliResultOutput } from "./streamedJsonOutput.js"; /** Register CLI equivalents of the Electron MCP tools. */ export const registerElectronCommands = ( cli: ReturnType, logger: Logger, + resultOutput?: CliResultOutput, ): void => { registerElectronObservationCommands(cli, logger); registerElectronActiveCommand(cli, logger); - registerJavaScriptApplicationCommand(cli, logger); + registerJavaScriptApplicationCommand(cli, logger, resultOutput); registerJavaScriptRuntimeReconciliationCommand(cli, logger); }; @@ -56,9 +59,18 @@ const registerElectronActiveCommand = ( ); if (!input.ok) return input.error; const parsed = electronActiveObservationInputSchema.safeParse( - input.value, + resolveCliJsonPaths(input.value, [ + ["executable_path"], + ["application_path"], + ["application_root"], + ]), ); - if (!parsed.success) return inputError("capture_electron_scenario"); + if (!parsed.success) + return inputError( + "capture_electron_scenario", + parsed.error.issues, + input.value, + ); const { createElectronScenarioProvider } = await import("../composition/electronScenario.js"); const result = await captureElectronScenario( @@ -162,6 +174,7 @@ const registerElectronPageInspection = ( const registerJavaScriptApplicationCommand = ( cli: ReturnType, logger: Logger, + resultOutput?: CliResultOutput, ): void => { cli.command(CLI_COMMANDS.analyzeJavaScriptApplication, { description: @@ -170,12 +183,18 @@ const registerJavaScriptApplicationCommand = ( path: z.string().describe("ASAR or extracted application path"), }), options: javascriptApplicationOptions, - run: ({ args, options }) => + run: ({ args, options, format }) => logCliCommand(logger, CLI_COMMANDS.analyzeJavaScriptApplication, () => - runCliJavaScriptApplicationAnalysis({ - input_path: args.path, - format: options.artifactFormat, - }), + runCliJavaScriptApplicationAnalysis( + { input_path: args.path, format: options.artifactFormat }, + resultOutput === undefined + ? undefined + : { + output: resultOutput, + command: CLI_COMMANDS.analyzeJavaScriptApplication, + format, + }, + ), ), }); }; @@ -186,8 +205,18 @@ const electronObservationContext = async () => { return { provider: createElectronObservationProvider() }; }; -const inputError = (operation: string): JsonValue => - cliError(new AnalysisInputError(operation)); +const inputError = ( + operation: string, + issues?: Parameters[0], + input?: unknown, +): JsonValue => + cliError( + new AnalysisInputError( + operation, + undefined, + issues === undefined ? [] : projectInputIssues(issues, input), + ), + ); const cliError = ( error: Parameters[0], diff --git a/src/cli/firmwareCommands.ts b/src/cli/firmwareCommands.ts index 961b44386..ad08c7804 100644 --- a/src/cli/firmwareCommands.ts +++ b/src/cli/firmwareCommands.ts @@ -1,4 +1,5 @@ import { z } from "incur"; +import { resolve } from "node:path"; import { FirmwareAnalysisService } from "../application/firmware/FirmwareAnalysisService.js"; import { createFirmwareAnalysisProvider } from "../composition/firmware.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; @@ -38,7 +39,8 @@ export const registerFirmwareCommands = ( execute( CLI_COMMANDS.inspectFirmwareRegions, "inspect_firmware_regions", - args, + // CLI paths are operator-relative; resolve before shared validation. + { path: resolve(args.path) }, ), }); cli.command(CLI_COMMANDS.extractFirmware, { @@ -81,8 +83,8 @@ export const registerFirmwareCommands = ( }), run: ({ args, options }) => execute(CLI_COMMANDS.extractFirmware, "extract_firmware", { - path: args.path, - output_directory: args.outputDirectory, + path: resolve(args.path), + output_directory: resolve(args.outputDirectory), ...(options.offset === undefined && options.length === undefined ? {} : { range: { offset: options.offset, length: options.length } }), diff --git a/src/cli/javascriptApplicationAnalysis.ts b/src/cli/javascriptApplicationAnalysis.ts index b1f08da9d..92d4696c4 100644 --- a/src/cli/javascriptApplicationAnalysis.ts +++ b/src/cli/javascriptApplicationAnalysis.ts @@ -1,13 +1,33 @@ import { analyzeJavaScriptApplication } from "../application/javascript/JavaScriptApplicationService.js"; +import { createProgressReporter } from "../application/ProgressReporter.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import type { JsonValue } from "../domain/jsonValue.js"; +import type { CliCommandOutput } from "./streamedJsonOutput.js"; /** Execute the shared one-shot CLI boundary for static JavaScript analysis. */ export const runCliJavaScriptApplicationAnalysis = async ( input: unknown, -): Promise => { - const result = await analyzeJavaScriptApplication(input); - return result.ok ? result.value : cliError(result.error); + output?: CliCommandOutput, +): Promise => { + const started = performance.now(); + const progress = createProgressReporter( + async (update) => { + process.stderr.write(`${JSON.stringify({ rea_progress: update })}\n`); + }, + { minimumIntervalMs: 0 }, + ); + const result = await analyzeJavaScriptApplication(input, { progress }); + const value = result.ok ? result.value : cliError(result.error); + if ( + output !== undefined && + (await output.output.write(value, { + command: output.command, + format: output.format, + duration: `${Math.round(performance.now() - started)}ms`, + })) + ) + return undefined; + return value; }; const cliError = ( diff --git a/src/cli/jsonOutput.ts b/src/cli/jsonOutput.ts new file mode 100644 index 000000000..0e7e75503 --- /dev/null +++ b/src/cli/jsonOutput.ts @@ -0,0 +1,149 @@ +import type { Writable } from "node:stream"; + +type JsonOutputFormat = "json" | "jsonl"; + +/** Write ordinary JSON without constructing a document-sized string. */ +export const writeJsonOutput = async ( + value: unknown, + destination: Writable, + format: JsonOutputFormat = "json", +): Promise => { + for (const part of bufferedParts(documentParts(value, format))) + await writePart(destination, part); +}; + +const writePart = (destination: Writable, part: string): Promise => + new Promise((resolve, reject) => { + const onError = (cause: unknown): void => reject(cause); + destination.once("error", onError); + try { + destination.write(part, (cause) => { + if (cause) { + // A Writable can emit its error after invoking the write callback. + setImmediate(() => destination.off("error", onError)); + reject(cause); + } else { + destination.off("error", onError); + resolve(); + } + }); + } catch (cause: unknown) { + destination.off("error", onError); + reject(cause); + } + }); + +function* documentParts( + value: unknown, + format: JsonOutputFormat, +): Generator { + if (format === "jsonl" && Array.isArray(value)) { + for (const item of value) { + yield* encodeValue(item, false, 0, new Set()); + yield "\n"; + } + return; + } + yield* encodeValue(value, format === "json", 0, new Set()); + yield "\n"; +} + +function* bufferedParts(parts: Iterable): Generator { + let buffer = ""; + for (const part of parts) { + if (buffer.length + part.length > 64 * 1024 && buffer !== "") { + yield buffer; + buffer = ""; + } + buffer += part; + } + if (buffer !== "") yield buffer; +} + +function* encodeString(value: string): Generator { + yield '"'; + for (let start = 0; start < value.length;) { + let end = Math.min(start + 8192, value.length); + const last = value.charCodeAt(end - 1); + const next = value.charCodeAt(end); + // Keep a surrogate pair in the same JSON.stringify and UTF-8 write. + if ( + end < value.length && + last >= 0xd800 && + last <= 0xdbff && + next >= 0xdc00 && + next <= 0xdfff + ) + end -= 1; + yield JSON.stringify(value.slice(start, end)).slice(1, -1); + start = end; + } + yield '"'; +} + +function* encodeValue( + value: unknown, + pretty: boolean, + depth: number, + ancestors: Set, +): Generator { + if (typeof value === "string") { + yield* encodeString(value); + return; + } + if (value === null || typeof value === "boolean") { + yield value === null ? "null" : String(value); + return; + } + if (typeof value === "number" && Number.isFinite(value)) { + yield String(value); + return; + } + if (typeof value !== "object" || value === null) + throw new TypeError("CLI JSON output requires ordinary JSON values"); + if (ancestors.has(value)) + throw new TypeError("CLI JSON output contains a circular reference"); + if ( + !Array.isArray(value) && + Object.prototype.toString.call(value) !== "[object Object]" + ) + throw new TypeError("CLI JSON output requires ordinary JSON objects"); + ancestors.add(value); + try { + const array = Array.isArray(value); + const keys = array + ? undefined + : Object.keys(value).filter((key) => { + const item: unknown = Reflect.get(value, key); + // Incur's filter can select inherited methods. Match native JSON's + // omission of fields that have no JSON representation. + return ( + item !== undefined && + typeof item !== "function" && + typeof item !== "symbol" + ); + }); + const count = array ? value.length : (keys?.length ?? 0); + yield array ? "[" : "{"; + for (let index = 0; index < count; index += 1) { + if (index > 0) yield ","; + if (pretty) yield `\n${" ".repeat(depth + 1)}`; + let item: unknown; + if (array) { + item = value[index]; + } else { + const key = keys?.[index]; + if (key === undefined) + throw new TypeError("CLI JSON object changed during serialization"); + yield* encodeString(key); + yield pretty ? ": " : ":"; + item = Reflect.get(value, key); + } + yield* encodeValue(item, pretty, depth + 1, ancestors); + } + if (pretty && count > 0) yield `\n${" ".repeat(depth)}`; + yield array ? "]" : "}"; + } finally { + ancestors.delete(value); + } +} diff --git a/src/cli/processCommands.ts b/src/cli/processCommands.ts index e68059b7c..af10e95c8 100644 --- a/src/cli/processCommands.ts +++ b/src/cli/processCommands.ts @@ -4,7 +4,7 @@ import { captureProcessScenarioFile, compareProcessEvidenceFiles, isProcessCliFailure, -} from "../application/ProcessCli.js"; +} from "../application/process/ProcessCli.js"; import { logCliCommand } from "../cliLogging.js"; import type { Logger } from "../logger.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; diff --git a/src/cli/streamedJsonOutput.ts b/src/cli/streamedJsonOutput.ts new file mode 100644 index 000000000..282316d0a --- /dev/null +++ b/src/cli/streamedJsonOutput.ts @@ -0,0 +1,84 @@ +import { type Writable } from "node:stream"; + +import { Filter } from "incur"; + +import { parseCliOutputArguments } from "../cliOutput.js"; +import type { JsonValue } from "../domain/jsonValue.js"; +import { writeJsonOutput } from "./jsonOutput.js"; + +/** Result metadata supplied by the command that actually ran. */ +export interface CliResultOutputMetadata { + readonly command: string; + readonly duration: string; + readonly format: string; +} + +/** Optional executable-owned result surface for one CLI invocation. */ +export interface CliResultOutput { + readonly handled: boolean; + readonly failed: boolean; + write(value: JsonValue, metadata: CliResultOutputMetadata): Promise; +} + +/** Selected command output context, independent of application workflows. */ +export interface CliCommandOutput { + readonly output: CliResultOutput; + readonly command: string; + readonly format: string; +} + +/** Preserve Incur's JSON controls while streaming the complete result. */ +export const createStreamedCliJsonOutput = ( + arguments_: readonly string[], + destination: Writable, +): CliResultOutput | undefined => { + const options = parseCliOutputArguments(arguments_); + if ( + options.parseError || + options.tokenCount || + options.tokenWindow || + (options.format !== "json" && options.format !== "jsonl") + ) + return undefined; + const format = options.format; + let handled = false; + let failed = false; + return { + get handled() { + return handled; + }, + get failed() { + return failed; + }, + async write(value, metadata) { + if (metadata.format !== format) return false; + const filtered: unknown = options.filterOutput + ? Filter.apply(value, Filter.parse(options.filterOutput)) + : value; + const meta = { + command: metadata.command, + duration: metadata.duration, + }; + const document = options.fullOutput + ? filtered === undefined + ? { ok: true, meta } + : { ok: true, data: filtered, meta } + : filtered === undefined || + filtered === null || + typeof filtered === "function" || + typeof filtered === "symbol" + ? {} + : filtered; + // Once writing starts, a failed destination must not receive a second + // document from Incur's error formatter. + handled = true; + try { + await writeJsonOutput(document, destination, format); + } catch (cause: unknown) { + failed = true; + throw cause; + } + return true; + }, + }; +}; diff --git a/src/cli/utilityCommands.ts b/src/cli/utilityCommands.ts index 135c92829..ae31f4b41 100644 --- a/src/cli/utilityCommands.ts +++ b/src/cli/utilityCommands.ts @@ -1,5 +1,4 @@ import { z } from "incur"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { runCapabilityStatus, @@ -10,6 +9,10 @@ import { importReferenceSource } from "../application/ReferenceSourceImport.js"; import { projectReferenceSourceImportError } from "../application/ReferenceSourceImportTypes.js"; import { parseConfig } from "../config.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; +import { AnalysisInputError } from "../domain/analysisErrorCore.js"; +import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { safeParseJson } from "../domain/safeJson.js"; +import { nativeUiScenarioInputSchema } from "../domain/native/nativeUiObservation.js"; import { PRODUCT_IDENTITY } from "../identity.js"; import { logCliCommand } from "../cliLogging.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; @@ -69,33 +72,59 @@ export const registerUtilityCommands = ( maxNodes: "max-nodes", }, run: ({ args, options }) => - logCliCommand(logger, command, () => - runProviderAnalysis( - args.path, - operation, - { - pid: options.pid, - window_id: options.windowId, - screenshot: options.screenshot, - accessibility: options.accessibility, - max_nodes: options.maxNodes, - ...(operation === "capture_native_ui_scenario" - ? { - steps: - options.steps === undefined - ? [] - : jsonValueSchema.parse(JSON.parse(options.steps)), - } - : {}), - }, - logger, - ), - ), + logCliCommand(logger, command, async () => { + const parameters = { + pid: options.pid, + window_id: options.windowId, + screenshot: options.screenshot, + accessibility: options.accessibility, + max_nodes: options.maxNodes, + }; + if (operation === "capture_native_ui_scenario") { + const decoded = + options.steps === undefined + ? { ok: true as const, value: undefined } + : safeParseJson(options.steps); + if (!decoded.ok) + return invalidNativeUiScenarioInput(operation, [ + { path: ["steps"], reason: "invalid_format", expected: "JSON" }, + ]); + const parsed = nativeUiScenarioInputSchema.safeParse({ + ...parameters, + steps: decoded.value, + }); + if (!parsed.success) + return invalidNativeUiScenarioInput( + operation, + projectInputIssues(parsed.error.issues, { + ...parameters, + ...(options.steps === undefined + ? {} + : { steps: decoded.value }), + }), + ); + return runProviderAnalysis( + args.path, + operation, + parsed.data, + logger, + ); + } + return runProviderAnalysis(args.path, operation, parameters, logger); + }), }); } registerReferenceSourceCommand(cli, logger, environment); }; +const invalidNativeUiScenarioInput = ( + operation: string, + issues: ConstructorParameters[2], +) => ({ + error: "Application workflow failed", + ...projectAnalysisError(new AnalysisInputError(operation, undefined, issues)), +}); + const registerCapabilityCommands = (cli: CliInstance, logger: Logger): void => { for (const command of [ CLI_COMMANDS.capabilities, diff --git a/src/cli/webNetworkCaptureCommands.ts b/src/cli/webNetworkCaptureCommands.ts new file mode 100644 index 000000000..a5601b57b --- /dev/null +++ b/src/cli/webNetworkCaptureCommands.ts @@ -0,0 +1,60 @@ +import { z } from "incur"; +import { createWebNetworkCaptureService } from "../composition/webNetworkCaptures.js"; +import { CLI_COMMANDS } from "../cliCommandNames.js"; +import { logCliCommand } from "../cliLogging.js"; +import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; +import type { Logger } from "../logger.js"; +import type { CliInstance } from "./types.js"; +import { withCommandCancellation } from "./commandCancellation.js"; + +/** Expose offline historical capture inspection with the same selection contract as MCP. */ +export const registerWebNetworkCaptureCommands = ( + cli: CliInstance, + logger: Logger, + environment: Readonly, +): void => { + const service = createWebNetworkCaptureService(environment); + cli.command(CLI_COMMANDS.inspectWebNetworkCapture, { + description: + "Inspect retained HAR or native mitmproxy records without fetching recorded URLs", + args: z.object({ + capturePath: z.string().describe("Absolute local capture path"), + format: z.enum(["har", "mitmproxy"]).describe("Producer capture format"), + }), + options: z.object({ + record: z + .array(z.coerce.number().int().nonnegative()) + .optional() + .describe("Zero-based producer ordinals; repeat to select several"), + sensitiveValue: z + .array(z.string().min(1)) + .default([]) + .describe( + "Literal string values explicitly marked sensitive; repeat as needed", + ), + }), + run: ({ args, options }) => + withCommandCancellation((signal) => + logCliCommand( + logger, + CLI_COMMANDS.inspectWebNetworkCapture, + async () => { + const result = await service.inspect( + { + capture_path: args.capturePath, + format: args.format, + sensitive_values: options.sensitiveValue, + ...(options.record === undefined + ? {} + : { record_ordinals: options.record }), + }, + { signal }, + ); + return result.ok + ? result.value + : projectAnalysisError(result.error); + }, + ), + ), + }); +}; diff --git a/src/cliBrowserScenarioCommands.ts b/src/cliBrowserScenarioCommands.ts index 877eae813..23984effb 100644 --- a/src/cliBrowserScenarioCommands.ts +++ b/src/cliBrowserScenarioCommands.ts @@ -4,10 +4,11 @@ import { z } from "zod"; import { captureBrowserScenario } from "./application/BrowserScenarioCaptureService.js"; import { createBrowserScenarioProvider } from "./composition/browserScenario.js"; import { CLI_COMMANDS } from "./cliCommandNames.js"; -import { parseCliJsonInput } from "./cliJsonInput.js"; +import { parseCliJsonInput, resolveCliJsonPaths } from "./cliJsonInput.js"; import { logCliCommand } from "./cliLogging.js"; import { AnalysisInputError } from "./domain/analysisErrorCore.js"; import { projectAnalysisError } from "./domain/analysisErrorProjection.js"; +import { projectInputIssues } from "./domain/inputIssueProjection.js"; import { browserScenarioSchema } from "./domain/browserScenario.js"; import type { JsonValue } from "./domain/jsonValue.js"; import type { Logger } from "./logger.js"; @@ -31,9 +32,17 @@ export const registerBrowserScenarioCommands = ( logCliCommand(logger, CLI_COMMANDS.captureBrowserScenario, async () => { const input = await parseCliJsonInput(args.inputJson, OPERATION); if (!input.ok) return input.error; - const scenario = browserScenarioSchema.safeParse(input.value); + const scenario = browserScenarioSchema.safeParse( + resolveCliJsonPaths(input.value, [["browser", "executable_path"]]), + ); if (!scenario.success) - return cliError(new AnalysisInputError(OPERATION)); + return cliError( + new AnalysisInputError( + OPERATION, + { cause: scenario.error }, + projectInputIssues(scenario.error.issues, input.value), + ), + ); const result = await captureBrowserScenario( createBrowserScenarioProvider(), scenario.data, diff --git a/src/cliCommandNames.ts b/src/cliCommandNames.ts index 489ddafa5..4006827ea 100644 --- a/src/cliCommandNames.ts +++ b/src/cliCommandNames.ts @@ -1,5 +1,6 @@ /** Canonical names used by every one-shot CLI command registration. */ export const CLI_COMMANDS = Object.freeze({ + inspectWebNetworkCapture: "inspect-web-network-capture", observeWebExecution: "observe-web-execution", inspectWebEventListeners: "inspect-web-event-listeners", traceWebSourceLocation: "trace-web-source-location", diff --git a/src/cliEnvironmentSeam.test.ts b/src/cliEnvironmentSeam.test.ts index 4aa57dca3..6ad300860 100644 --- a/src/cliEnvironmentSeam.test.ts +++ b/src/cliEnvironmentSeam.test.ts @@ -4,26 +4,13 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { createCli } from "./cli.js"; -import { createSystemDoctorHost } from "./doctorRuntime.js"; -import { captureProcessScenarioFile } from "./application/ProcessCli.js"; +import { captureProcessScenarioFile } from "./application/process/ProcessCli.js"; import { runCapabilityStatus } from "./composition/directAnalysis.js"; -import { probeProcessCaptureCapability } from "./application/ProcessHarness.js"; +import { probeProcessCaptureCapability } from "./process/capture/ProcessHarness.js"; import { parseEvidence } from "./domain/evidence.js"; -import { parseProcessCapture } from "./domain/processCapture.js"; +import { parseProcessCapture } from "./domain/process/processCapture.js"; describe("the CLI takes its environment as an input", () => { - it("builds from an explicitly supplied environment", () => { - expect(createCli({ REA_LOG_LEVEL: "debug" })).toBeDefined(); - expect(createCli({})).toBeDefined(); - }); - - it("still builds with no environment supplied", () => { - // The default keeps production behaviour identical. - expect(createCli()).toBeDefined(); - expect(createSystemDoctorHost()).toBeDefined(); - }); - it("resolves from injected PATH and inherits injected env with scenario overrides", async ({ skip, }) => { diff --git a/src/cliHostInputs.test.ts b/src/cliHostInputs.test.ts index 8086f950d..96c7cd005 100644 --- a/src/cliHostInputs.test.ts +++ b/src/cliHostInputs.test.ts @@ -1,7 +1,5 @@ import { describe, expect, it } from "vitest"; -import { browserContext } from "./cliBrowserContext.js"; -import { CdpBrowserProvider } from "./browser/CdpBrowserProvider.js"; import { HopperProvider } from "./hopper/HopperProvider.js"; import { silentLogger } from "./logger.js"; import { parseConfig } from "./config.js"; @@ -35,9 +33,3 @@ describe("host platform is injected, not read from the ambient process", () => { }, ); }); - -describe("browser CLI provider setup", () => { - it("does not require permission policy configuration", () => { - expect(browserContext().provider).toBeInstanceOf(CdpBrowserProvider); - }); -}); diff --git a/src/cliJsonInput.ts b/src/cliJsonInput.ts index 370a2f1b0..44199b79e 100644 --- a/src/cliJsonInput.ts +++ b/src/cliJsonInput.ts @@ -1,4 +1,5 @@ import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; import { AnalysisInputError } from "./domain/analysisErrorCore.js"; import { projectAnalysisError } from "./domain/analysisErrorProjection.js"; @@ -15,8 +16,6 @@ export const parseCliJsonInput = async ( > => { const inline = parseJson(value); if (inline !== undefined) return { ok: true, value: inline }; - if (["{", "["].includes(value.trimStart()[0] ?? "")) - return { ok: false, error: inputError(operation) }; try { // Read raw bytes so invalid UTF-8 is rejected by parseJson instead of // being silently replaced by lossy "utf8" decoding. @@ -25,10 +24,61 @@ export const parseCliJsonInput = async ( ? jsonFileError(value, operation, "invalid-json") : { ok: true, value: parsed }; } catch (cause: unknown) { + if ( + ["{", "["].includes(value.trimStart()[0] ?? "") && + cannotBeAnExistingFile(cause) && + !hasExplicitJsonFileExtension(value) + ) + return { ok: false, error: inputError(operation) }; return jsonFileError(value, operation, "read-failed", cause); } }; +/** + * Resolve named string fields of CLI-supplied JSON against the operator + * working directory. Each entry is a key path from the document root; only + * fields holding strings are resolved, everything else passes through + * untouched. Shared MCP contracts reject relative paths, so CLI workflows + * resolve operator-relative values before validation. + */ +export const resolveCliJsonPaths = ( + value: unknown, + paths: ReadonlyArray>, +): unknown => { + let resolved = value; + for (const keys of paths) resolved = resolveJsonPath(resolved, keys); + return resolved; +}; + +const resolveJsonPath = ( + value: unknown, + keys: ReadonlyArray, +): unknown => { + const [head, ...tail] = keys; + if (typeof value !== "object" || value === null || Array.isArray(value)) + return value; + const entries = Object.entries(value).map( + ([key, entry]: readonly [string, unknown]): readonly [string, unknown] => { + if (key !== head) return [key, entry]; + if (tail.length === 0) + return [key, typeof entry === "string" ? resolve(entry) : entry]; + return [key, resolveJsonPath(entry, tail)]; + }, + ); + return Object.fromEntries(entries); +}; + +const cannotBeAnExistingFile = (cause: unknown): boolean => + typeof cause === "object" && + cause !== null && + "code" in cause && + (cause.code === "ENOENT" || + cause.code === "ENAMETOOLONG" || + cause.code === "ENOTDIR"); + +const hasExplicitJsonFileExtension = (value: string): boolean => + value.toLowerCase().endsWith(".json"); + const parseJson = (value: string | Uint8Array): unknown => { let text: string; if (typeof value === "string") { diff --git a/src/cliLogging.test.ts b/src/cliLogging.test.ts index c02cb2041..b37a6f183 100644 --- a/src/cliLogging.test.ts +++ b/src/cliLogging.test.ts @@ -24,7 +24,7 @@ import { type UninstallHost, } from "./application/Uninstall.js"; import { CLI_COMMANDS } from "./cliCommandNames.js"; -import { isProcessCliFailure } from "./application/ProcessCli.js"; +import { isProcessCliFailure } from "./application/process/ProcessCli.js"; describe("CLI operation status classification", () => { it.each([ diff --git a/src/cliObservationOptions.test.ts b/src/cliObservationOptions.test.ts deleted file mode 100644 index b6a7926de..000000000 --- a/src/cliObservationOptions.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { electronPageInspectionOptions } from "./cliObservationOptions.js"; - -describe("Electron CLI observation duration", () => { - it("accepts caller-selected windows longer than ten seconds", () => { - expect( - electronPageInspectionOptions.parse({ observationMs: 60_000 }) - .observationMs, - ).toBe(60_000); - }); -}); diff --git a/src/cliOutput.ts b/src/cliOutput.ts index 96f65a7e7..a5b359992 100644 --- a/src/cliOutput.ts +++ b/src/cliOutput.ts @@ -6,6 +6,7 @@ const UNSUPPORTED_OUTPUT_COMBINATION_MESSAGE = "Token windows cannot preserve structured output. Remove --token-limit/--token-offset and use --filter-output or command pagination."; type StructuredOutputFormat = "json" | "jsonl" | "yaml"; +const INCUR_OUTPUT_FORMATS = new Set(["toon", "json", "yaml", "md", "jsonl"]); export type CliOutputArgumentValidation = | { readonly ok: true } @@ -19,8 +20,11 @@ export type CliOutputArgumentValidation = type JsonRecord = Record; interface ParsedCliOutputArguments { - readonly filterOutput: boolean; + readonly filterOutput: string | undefined; readonly format: string; + readonly fullOutput: boolean; + readonly parseError: boolean; + readonly tokenCount: boolean; readonly tokenWindow: boolean; } @@ -34,12 +38,23 @@ const validationError = (value: JsonRecord): JsonRecord | undefined => { return undefined; }; -const parseCliOutputArguments = ( +/** Read effective Incur output controls at the executable boundary. */ +export const parseCliOutputArguments = ( arguments_: readonly string[], ): ParsedCliOutputArguments => { let format: string = "toon"; - let filterOutput = false; + let filterOutput: string | undefined; + let fullOutput = false; + let tokenCount = false; let tokenWindow = false; + const parsed = (parseError: boolean): ParsedCliOutputArguments => ({ + filterOutput, + format, + fullOutput, + parseError, + tokenCount, + tokenWindow: parseError ? false : tokenWindow, + }); for (let index = 0; index < arguments_.length; index += 1) { const argument = arguments_[index]; // Incur scans builtins across `--` and accepts only spaced value forms. @@ -50,40 +65,46 @@ const parseCliOutputArguments = ( } if (argument === "--format") { const value = arguments_[index + 1]; - if (value !== undefined) { + if (value) { + // Incur stops parsing at an invalid format before applying any later + // output controls. Let it report that malformed flag itself. + if (!INCUR_OUTPUT_FORMATS.has(value)) return parsed(true); format = value; index += 1; } continue; } if (argument === "--filter-output") { - if (arguments_[index + 1] !== undefined) { - filterOutput = true; + if (arguments_[index + 1]) { + filterOutput = arguments_[index + 1]; index += 1; } continue; } if (argument === "--token-limit" || argument === "--token-offset") { - if (arguments_[index + 1] !== undefined) { + const value = arguments_[index + 1]; + if (value) { + const numericValue = Number(value); + if (!Number.isFinite(numericValue) || value.trim() === "") + return parsed(true); tokenWindow = true; index += 1; } continue; } - if ( - argument?.startsWith("--token-limit=") || - argument?.startsWith("--token-offset=") - ) - tokenWindow = true; + if (argument === "--full-output") fullOutput = true; + if (argument === "--token-count") tokenCount = true; } - return { filterOutput, format, tokenWindow }; + return parsed(false); }; /** Reject text-window controls that would corrupt a structured document. */ export const validateCliOutputArguments = ( arguments_: readonly string[], ): CliOutputArgumentValidation => { - const { format, tokenWindow } = parseCliOutputArguments(arguments_); + const { format, parseError, tokenWindow } = + parseCliOutputArguments(arguments_); + if (parseError) return { ok: true }; if ( tokenWindow && (format === "json" || format === "jsonl" || format === "yaml") @@ -101,7 +122,9 @@ export const validateCliOutputArguments = ( export const renderEmptyFilteredCliOutput = ( arguments_: readonly string[], ): string | undefined => { - const { filterOutput, format } = parseCliOutputArguments(arguments_); + const { filterOutput, format, parseError } = + parseCliOutputArguments(arguments_); + if (parseError) return undefined; if ( filterOutput && (format === "json" || format === "jsonl" || format === "yaml") diff --git a/src/composition/webNetworkCaptures.ts b/src/composition/webNetworkCaptures.ts new file mode 100644 index 000000000..6dc7668d4 --- /dev/null +++ b/src/composition/webNetworkCaptures.ts @@ -0,0 +1,15 @@ +import { WebNetworkCaptureService } from "../application/WebNetworkCaptureService.js"; +import { HistoricalCaptureDecoder } from "../browser/history/HistoricalCaptureDecoder.js"; +import { HarCaptureAdapter } from "../browser/history/HarCaptureAdapter.js"; +import { MitmproxyCaptureAdapter } from "../browser/history/MitmproxyCaptureAdapter.js"; + +/** Compose offline format adapters without import-time reads, installation or process startup. */ +export const createWebNetworkCaptureService = ( + environment: Readonly = process.env, +): WebNetworkCaptureService => + new WebNetworkCaptureService( + new HistoricalCaptureDecoder( + [new HarCaptureAdapter(), new MitmproxyCaptureAdapter(environment)], + environment, + ), + ); diff --git a/src/config.test.ts b/src/config.test.ts index 42cef7172..303203c2f 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import { parseConfig } from "./config.js"; describe("runtime configuration", () => { - it("allows target-free startup and defaults to Hopper's documented launcher", () => { + it("allows target-free startup and applies runtime defaults", () => { const empty = parseConfig({}); expect(empty.ok).toBe(true); if (empty.ok) { @@ -13,11 +13,6 @@ describe("runtime configuration", () => { const result = parseConfig({ HOPPER_TARGET_PATH: "/usr/bin/true" }); expect(result.ok).toBe(true); if (result.ok) { - expect(result.value.hopperLauncherPath).toBe( - process.platform === "linux" - ? "/opt/hopper/bin/Hopper" - : "/Applications/Hopper Disassembler.app/Contents/MacOS/hopper", - ); expect(result.value.hopperTargetKind).toBe("executable"); expect(result.value.hopperLoaderArgs).toEqual([]); expect(result.value.logLevel).toBe("info"); diff --git a/src/config/parseConfig.ts b/src/config/parseConfig.ts index 61a9152ba..17fb465fb 100644 --- a/src/config/parseConfig.ts +++ b/src/config/parseConfig.ts @@ -1,13 +1,36 @@ +import { accessSync, constants } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; + import { ConfigurationError } from "../domain/configurationErrors.js"; import { ok, type Result } from "../domain/result.js"; import { parseEnvironment } from "./environment.js"; import { parseStringArray, parseLoaderArgs } from "./parsers.js"; import type { AppConfig } from "./types.js"; -const defaultHopperLauncherPath = (): string => - process.platform === "linux" - ? "/opt/hopper/bin/Hopper" - : "/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"; +const DEFAULT_HOPPER_LAUNCHER_PATH = + "/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"; +const SYSTEM_LINUX_HOPPER = "/opt/hopper/bin/Hopper"; + +const executableAvailable = (path: string): boolean => { + try { + accessSync(path, constants.X_OK); + return true; + } catch { + return false; + } +}; + +export const defaultHopperLauncherPath = ( + platform: NodeJS.Platform = process.platform, + homeDirectory: string = homedir(), + executable: (path: string) => boolean = executableAvailable, +): string => { + if (platform !== "linux") return DEFAULT_HOPPER_LAUNCHER_PATH; + if (executable(SYSTEM_LINUX_HOPPER)) return SYSTEM_LINUX_HOPPER; + const userLocal = join(homeDirectory, ".local/share/rea/hopper/bin/Hopper"); + return executable(userLocal) ? userLocal : SYSTEM_LINUX_HOPPER; +}; /** Parse provider configuration and runtime prerequisites at the composition root. */ export const parseConfig = ( diff --git a/src/contracts/browserCaptureToolInputSchema.ts b/src/contracts/browserCaptureToolInputSchema.ts new file mode 100644 index 000000000..bdc7dc148 --- /dev/null +++ b/src/contracts/browserCaptureToolInputSchema.ts @@ -0,0 +1,62 @@ +import { browserCaptureComparisonInputSchema } from "../domain/browserCaptureComparison.js"; +import { browserScenarioCaptureSchema } from "../domain/browserScenarioCapture.js"; +import { captureSnapshotSchema } from "../domain/webCaptureDiffSchemas.js"; + +// Captures are complete producer results to pass back unchanged, rather than +// analyst-authored options. Describe that boundary without embedding the entire +// inspection result schema in every comparison input. The cloned parser still +// validates every nested field using the canonical domain schemas. +const passiveCaptureInputSchema = captureSnapshotSchema.clone( + captureSnapshotSchema.def, +); +passiveCaptureInputSchema._zod.toJSONSchema = () => ({ + type: "object", + properties: { + inspection: { + type: "object", + description: + "Complete result object returned by inspect_web_page. Pass the entire result unchanged, including capture identity, observations, completeness, and limitations. Every nested field is validated by REA.", + }, + webmcp: { + anyOf: [{ type: "object" }, { type: "null" }], + default: null, + description: + "Complete result object returned by discover_webmcp_tools for this capture, or null when it was not recorded. Pass the entire result unchanged. Every nested field is validated by REA.", + }, + }, + required: ["inspection"], +}); + +const scenarioCaptureInputSchema = browserScenarioCaptureSchema.clone( + browserScenarioCaptureSchema.def, +); +scenarioCaptureInputSchema._zod.toJSONSchema = () => ({ + type: "object", + description: + "Complete result object returned by capture_browser_scenario. Pass the entire result unchanged, including browser and scenario identity, steps, events, artifact contents and digests, completeness, and limitations. Every nested field is validated by REA.", +}); + +/** Advertise capture round trips while retaining full runtime validation. */ +export const browserCaptureToolInputSchema = + browserCaptureComparisonInputSchema.safeExtend({ + before: passiveCaptureInputSchema + .optional() + .describe( + "Earlier passive web-page capture with complete producer results.", + ), + after: passiveCaptureInputSchema + .optional() + .describe( + "Later passive web-page capture with complete producer results.", + ), + before_scenario: scenarioCaptureInputSchema + .optional() + .describe( + "Earlier complete result object returned by capture_browser_scenario.", + ), + after_scenario: scenarioCaptureInputSchema + .optional() + .describe( + "Later complete result object returned by capture_browser_scenario.", + ), + }); diff --git a/src/contracts/browserToolContracts.ts b/src/contracts/browserToolContracts.ts index ea16ce099..445d52e81 100644 --- a/src/contracts/browserToolContracts.ts +++ b/src/contracts/browserToolContracts.ts @@ -17,10 +17,7 @@ import { discoverWebMcpToolsInputSchema, webMcpDiscoverySchema, } from "../domain/webMcpDiscovery.js"; -import { - browserCaptureComparisonInputSchema, - browserCaptureComparisonSchema, -} from "../domain/browserCaptureComparison.js"; +import { browserCaptureComparisonSchema } from "../domain/browserCaptureComparison.js"; import { captureWebScreenshotInputSchema, compareWebScreenshotsInputSchema, @@ -29,6 +26,7 @@ import { } from "../domain/webScreenshot.js"; import { toolContractMetadata } from "./toolEffects.js"; import { evidenceResultOf } from "./toolOutputSchemas.js"; +import { browserCaptureToolInputSchema } from "./browserCaptureToolInputSchema.js"; const evidenceResult = evidenceResultOf; const listOutputSchema = evidenceResult(browserTargetListSchema); @@ -215,9 +213,9 @@ export const BROWSER_TOOL_CONTRACTS = [ name: "compare_web_captures", ...toolContractMetadata("compare_web_captures"), description: - "Compare passive web captures by providing before and after, or compare recorded scenarios by providing before_scenario and after_scenario with an optional normalization policy. Use exactly one input group. Scenario comparison aligns exact step IDs, records deterministic literal normalization, and returns every artifact-level difference plus alignment failures inline. Missing or truncated evidence never proves equality.", + "Compare passive web captures by providing before and after, each with the complete inspect_web_page result in inspection and an optional complete discover_webmcp_tools result in webmcp; or compare recorded scenarios by passing complete capture_browser_scenario results in before_scenario and after_scenario with an optional normalization policy. Use exactly one input group. Scenario comparison aligns exact step IDs, records deterministic literal normalization, and returns every artifact-level difference plus alignment failures inline. Missing or truncated evidence never proves equality.", kind: "browser-provider", - inputSchema: browserCaptureComparisonInputSchema, + inputSchema: browserCaptureToolInputSchema, outputSchema: captureDiffOutputSchema, examples: [ { diff --git a/src/contracts/contractSnapshot.test.ts b/src/contracts/contractSnapshot.test.ts index 1de10c515..f923cc3e9 100644 --- a/src/contracts/contractSnapshot.test.ts +++ b/src/contracts/contractSnapshot.test.ts @@ -16,6 +16,7 @@ import { ELECTRON_TOOL_CONTRACTS } from "./javascript/electronToolContracts.js"; import { JAVASCRIPT_RUNTIME_OBSERVATION_TOOL_CONTRACTS } from "./javascript/javascriptRuntimeObservationToolContracts.js"; import { APPLICATION_TOOL_CONTRACTS } from "./applicationToolContracts.js"; import { WEB_RUNTIME_TOOL_CONTRACTS } from "./webRuntimeToolContracts.js"; +import { WEB_NETWORK_CAPTURE_TOOL_CONTRACTS } from "./webNetworkCaptureToolContracts.js"; import { WEB_SCRIPT_TOOL_CONTRACTS } from "./webScriptToolContracts.js"; import { JAVASCRIPT_RECOVERY_TOOL_CONTRACTS } from "./javascript/javascriptRecoveryToolContracts.js"; import { TOOL_EFFECTS } from "./toolEffects.js"; @@ -80,6 +81,7 @@ describe("tool contract surface", () => { ...APPLICATION_TOOL_CONTRACTS, ...WEB_SCRIPT_TOOL_CONTRACTS, ...WEB_RUNTIME_TOOL_CONTRACTS, + ...WEB_NETWORK_CAPTURE_TOOL_CONTRACTS, ...JAVASCRIPT_RECOVERY_TOOL_CONTRACTS, ...SESSION_TOOL_CONTRACTS, ].map(({ name }) => name); diff --git a/src/contracts/errorSchemas.ts b/src/contracts/errorSchemas.ts index 7da9c1916..3ea7f5c76 100644 --- a/src/contracts/errorSchemas.ts +++ b/src/contracts/errorSchemas.ts @@ -1,5 +1,7 @@ import { z } from "zod"; +import { jsonValueSchema } from "../domain/jsonValue.js"; + const categorySchema = z.enum([ "invalid_input", "unsupported_provider", @@ -21,7 +23,7 @@ const common = { retryable: z.boolean(), remediation: remediationSchema, }; -const genericDetails = z.record(z.string(), z.json()).optional(); +const genericDetails = z.record(z.string(), jsonValueSchema).optional(); const generic = (code: Code) => z .object({ code: z.literal(code), ...common, details: genericDetails }) @@ -30,6 +32,8 @@ const generic = (code: Code) => /** Stable discriminated schema shared by every CLI and MCP error surface. */ export const analysisErrorProjectionSchema = z.discriminatedUnion("code", [ generic("invalid_request"), + generic("access_denied"), + generic("artifact_changed"), generic("unreadable_output"), generic("capability_unavailable"), generic("provider_unavailable"), diff --git a/src/contracts/investigationExamples.ts b/src/contracts/investigationExamples.ts index 72a61f721..752f58742 100644 --- a/src/contracts/investigationExamples.ts +++ b/src/contracts/investigationExamples.ts @@ -4,9 +4,9 @@ import { jsonValueSchema } from "../domain/jsonValue.js"; import { compareProcessCaptures, parseProcessCapture, -} from "../domain/processCapture.js"; +} from "../domain/process/processCapture.js"; import { FUNCTION_COMPARISON_EXAMPLE } from "./functionComparisonExample.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCaptureExample.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./process/processCaptureExample.js"; const comparison = compareFunctions( FUNCTION_COMPARISON_EXAMPLE.left, diff --git a/src/contracts/managed/managedToolContracts.ts b/src/contracts/managed/managedToolContracts.ts index dc5702322..85b5c1950 100644 --- a/src/contracts/managed/managedToolContracts.ts +++ b/src/contracts/managed/managedToolContracts.ts @@ -4,16 +4,21 @@ import type { ToolContract } from "../toolContracts.js"; import { managedOutputSchemas } from "../toolOutputSchemas.js"; import { toolContractMetadata } from "../toolEffects.js"; import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js"; +import { isAbsoluteLocalPath } from "../../domain/localPath.js"; /** Exact caller boundary for execution-free PE/CLI triage and identity. */ -const managedTargetInputSchema = z.object({ +const managedTargetInputSchema = z.strictObject({ path: z .string() .min(1) - .optional() + .refine(isAbsoluteLocalPath, { + message: + "path must be an absolute local filesystem path (for example /tmp/Example.dll or C:\\Example.dll)", + }) .describe( - "Explicit managed PE/CLI path; omit to reuse the previously selected managed target", - ), + "Explicit absolute managed PE/CLI path; omit to reuse the previously selected managed target", + ) + .optional(), }); export const managedArtifactInputSchema = managedTargetInputSchema; diff --git a/src/contracts/native/nativeToolContracts.ts b/src/contracts/native/nativeToolContracts.ts index 1a783043f..afeb9f652 100644 --- a/src/contracts/native/nativeToolContracts.ts +++ b/src/contracts/native/nativeToolContracts.ts @@ -44,12 +44,13 @@ const native = ( inputSchema: Schema, ) => { const outputSchema = requireOutputSchema(nativeOutputSchemas, name); + const strictInputSchema = inputSchema.strict(); return { name, ...toolContractMetadata(name), description, kind: "native-provider", - inputSchema, + inputSchema: strictInputSchema, outputSchema, examples: [ { @@ -59,7 +60,7 @@ const native = ( .parse(examples[name] ?? {}), }, ], - } satisfies ToolContract; + } satisfies ToolContract; }; /** Ordered single-line symbols; `swift-demangle` ends each result with LF. */ diff --git a/src/contracts/officialToolContracts.ts b/src/contracts/officialToolContracts.ts index 45efe5072..6b69c8053 100644 --- a/src/contracts/officialToolContracts.ts +++ b/src/contracts/officialToolContracts.ts @@ -24,16 +24,18 @@ const official = ( name: Name, description: string, inputSchema: Schema, -) => - ({ +) => { + const strictInputSchema = inputSchema.strict(); + return { name, ...toolContractMetadata(name), description, kind: "official-proxy", - inputSchema, + inputSchema: strictInputSchema, outputSchema: requireOutputSchema(officialOutputSchemas, name), - examples: examplesFor(name, inputSchema), - }) satisfies ToolContract; + examples: examplesFor(name, strictInputSchema), + } satisfies ToolContract; +}; /** Bridge operations exposed without additional application composition. */ export const OFFICIAL_TOOL_CONTRACTS = [ diff --git a/src/contracts/process/processCaptureExample.ts b/src/contracts/process/processCaptureExample.ts new file mode 100644 index 000000000..3288fdac1 --- /dev/null +++ b/src/contracts/process/processCaptureExample.ts @@ -0,0 +1,2 @@ +/** Minimal valid process capture retained as a public contract example. */ +export { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../domain/process/processCaptureExample.js"; diff --git a/src/contracts/processCaptureExample.ts b/src/contracts/processCaptureExample.ts deleted file mode 100644 index 5aed2939c..000000000 --- a/src/contracts/processCaptureExample.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** Minimal valid process capture retained as a public contract example. */ -export { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCaptureExample.js"; diff --git a/src/contracts/sessionLifecycleInputs.ts b/src/contracts/sessionLifecycleInputs.ts index fd0a1d58d..68b61b35f 100644 --- a/src/contracts/sessionLifecycleInputs.ts +++ b/src/contracts/sessionLifecycleInputs.ts @@ -1,21 +1,46 @@ import { executableFormatHintSchema } from "../domain/dosCom.js"; +import { isAbsoluteLocalPath } from "../domain/localPath.js"; import { z } from "zod"; import { analysisProviderSelectorSchema } from "./providerSelection.js"; +/** Caller-supplied analysis snapshot path; must name its destination absolutely. */ +const snapshotPathSchema = z + .string() + .min(1) + .refine(isAbsoluteLocalPath, { + message: + "snapshot_path must be an absolute local filesystem path (for example /tmp/rea/analysis.json or C:\\rea\\analysis.json)", + }) + .describe( + "Absolute local filesystem path for the analysis snapshot; relative paths are rejected.", + ); + +/** Caller-supplied analysis target path; must name the target absolutely. */ +const binaryTargetPathSchema = z + .string() + .min(1) + .refine(isAbsoluteLocalPath, { + message: + "path must be an absolute local filesystem path (for example /tmp/fixture.bin or C:\\analysis\\fixture.bin)", + }) + .describe( + "Absolute local filesystem path for the analysis target; relative paths are rejected.", + ); + /** Input contract for opening a target with an optional staged snapshot. */ -export const openBinaryInputSchema = z.object({ - path: z.string().min(1), +export const openBinaryInputSchema = z.strictObject({ + path: binaryTargetPathSchema, format: executableFormatHintSchema .optional() .describe( "Explicit headerless DOS COM interpretation; omission preserves header-based detection", ), provider_id: analysisProviderSelectorSchema.optional(), - snapshot_path: z.string().min(1).optional(), + snapshot_path: snapshotPathSchema.optional(), }); /** Input contract for closing a target after an optional atomic snapshot. */ -export const closeBinaryInputSchema = z.object({ - snapshot_path: z.string().min(1).optional(), +export const closeBinaryInputSchema = z.strictObject({ + snapshot_path: snapshotPathSchema.optional(), overwrite: z.boolean().default(false), }); diff --git a/src/contracts/sessionPathInputs.test.ts b/src/contracts/sessionPathInputs.test.ts new file mode 100644 index 000000000..6f4155824 --- /dev/null +++ b/src/contracts/sessionPathInputs.test.ts @@ -0,0 +1,167 @@ +import { describe, expect, it } from "vitest"; + +import { browserScenarioBrowserSchema } from "../domain/browserScenarioValues.js"; +import { firmwareInputSchemas } from "../domain/firmware/firmwareAnalysis.js"; +import { electronActiveObservationInputSchema } from "../domain/javascript/electronActiveObservation.js"; +import { managedArtifactInputSchema } from "./managed/managedToolContracts.js"; +import { exportEvidenceBundleInputSchema } from "./sessionToolContracts.js"; +import { + closeBinaryInputSchema, + openBinaryInputSchema, +} from "./sessionLifecycleInputs.js"; +import { importEvidenceBundleInputSchema } from "./sessionToolSchemas.js"; +import { binarySessionInputSchema } from "./sessionStatusContract.js"; + +type PathCase = { + readonly name: string; + readonly accepts: (value: unknown) => boolean; + readonly relative: unknown[]; + readonly absolute: string; + readonly omitted?: () => boolean; +}; + +const cases: readonly PathCase[] = [ + { + name: "open binary snapshot", + accepts: (snapshot_path) => + openBinaryInputSchema.safeParse({ path: "/tmp/fixture", snapshot_path }) + .success, + relative: ["relative/analysis.json", "../outside/analysis.json"], + absolute: "/tmp/rea/analysis.json", + omitted: () => + openBinaryInputSchema.safeParse({ path: "/tmp/fixture" }).success, + }, + { + name: "close binary snapshot", + accepts: (snapshot_path) => + closeBinaryInputSchema.safeParse({ snapshot_path }).success, + relative: ["analysis.json"], + absolute: "/tmp/analysis.json", + omitted: () => closeBinaryInputSchema.safeParse({}).success, + }, + { + name: "evidence bundle export", + accepts: (path) => + exportEvidenceBundleInputSchema.safeParse({ path }).success, + relative: ["out/bundle.json", "../../bundle.json"], + absolute: "/tmp/bundle.json", + }, + { + name: "managed target", + accepts: (path) => managedArtifactInputSchema.safeParse({ path }).success, + relative: ["bin/app.dll"], + absolute: "/tmp/app.dll", + omitted: () => managedArtifactInputSchema.safeParse({}).success, + }, + { + name: "open binary target", + accepts: (path) => openBinaryInputSchema.safeParse({ path }).success, + relative: ["fixtures/app.bin"], + absolute: "/tmp/fixture.bin", + }, + { + name: "evidence bundle import", + accepts: (path) => + importEvidenceBundleInputSchema.safeParse({ path }).success, + relative: ["evidence.json"], + absolute: "/tmp/evidence.json", + }, + { + name: "firmware inspection", + accepts: (path) => + firmwareInputSchemas.inspect_firmware_regions.safeParse({ path }).success, + relative: ["firmware.bin"], + absolute: "/tmp/firmware.bin", + }, + { + name: "firmware extraction", + accepts: (path) => + firmwareInputSchemas.extract_firmware.safeParse({ + path, + output_directory: "/tmp/firmware-output", + }).success, + relative: ["firmware.bin"], + absolute: "/tmp/firmware.bin", + }, + { + name: "browser executable", + accepts: (executable_path) => + browserScenarioBrowserSchema.safeParse({ + mode: "launch", + executable_path, + }).success, + relative: ["chrome"], + absolute: "/opt/chromium/chrome", + }, + { + name: "Electron executable", + accepts: (executable_path) => + electronActiveObservationInputSchema.safeParse({ + executable_path, + application_path: "/tmp/electron-app/main.js", + }).success, + relative: ["Electron"], + absolute: "/Applications/Electron.app/Contents/MacOS/Electron", + }, + { + name: "Electron application", + accepts: (application_path) => + electronActiveObservationInputSchema.safeParse({ + executable_path: "/Applications/Electron.app/Contents/MacOS/Electron", + application_path, + }).success, + relative: ["main.js"], + absolute: "/tmp/electron-app/main.js", + }, + { + name: "Electron application root", + accepts: (application_root) => + electronActiveObservationInputSchema.safeParse({ + executable_path: "/Applications/Electron.app/Contents/MacOS/Electron", + application_path: "/tmp/electron-app/main.js", + application_root, + }).success, + relative: ["app"], + absolute: "/tmp/electron-app", + omitted: () => + electronActiveObservationInputSchema.safeParse({ + executable_path: "/Applications/Electron.app/Contents/MacOS/Electron", + application_path: "/tmp/electron-app/main.js", + }).success, + }, + { + name: "expected server", + accepts: (expected_server_path) => + binarySessionInputSchema.safeParse({ expected_server_path }).success, + relative: ["dist/main.js"], + absolute: "/opt/rea/dist/main.js", + omitted: () => binarySessionInputSchema.safeParse({}).success, + }, +]; + +it.each(cases)("$name requires an absolute local path", (pathCase) => { + for (const relative of pathCase.relative) + expect(pathCase.accepts(relative), pathCase.name).toBe(false); + expect(pathCase.accepts(pathCase.absolute), pathCase.name).toBe(true); + if (pathCase.omitted !== undefined) + expect(pathCase.omitted(), pathCase.name).toBe(true); +}); + +it("keeps evidence bundle export overwrite disabled by default", () => { + expect( + exportEvidenceBundleInputSchema.safeParse({ path: "/tmp/bundle.json" }), + ).toMatchObject({ success: true, data: { overwrite: false } }); +}); + +describe.runIf(process.platform === "win32")( + "Windows absolute path forms", + () => { + it.each([ + [closeBinaryInputSchema, { snapshot_path: "C:\\rea\\analysis.json" }], + [closeBinaryInputSchema, { snapshot_path: "C:/rea/analysis.json" }], + [exportEvidenceBundleInputSchema, { path: "C:/rea/bundle.json" }], + ])("accepts %o", (schema, input) => { + expect(schema.safeParse(input).success).toBe(true); + }); + }, +); diff --git a/src/contracts/sessionStatusContract.ts b/src/contracts/sessionStatusContract.ts index c19c73a3b..303034c9c 100644 --- a/src/contracts/sessionStatusContract.ts +++ b/src/contracts/sessionStatusContract.ts @@ -1,11 +1,23 @@ import { z } from "zod"; +import { isAbsoluteLocalPath } from "../domain/localPath.js"; + /** Optional expectations used to compare the live session with its caller. */ -export const binarySessionInputSchema = z.object({ +export const binarySessionInputSchema = z.strictObject({ expected_package_version: z.string().min(1).optional(), expected_catalog_digest: z .string() .regex(/^[a-f0-9]{64}$/u) .optional(), - expected_server_path: z.string().min(1).optional(), + expected_server_path: z + .string() + .min(1) + .refine(isAbsoluteLocalPath, { + message: + "expected_server_path must be an absolute local filesystem path (for example /opt/rea/dist/main.js)", + }) + .describe( + "Absolute local filesystem path expected for the running server; relative paths are rejected.", + ) + .optional(), }); diff --git a/src/contracts/sessionToolContracts.ts b/src/contracts/sessionToolContracts.ts index 81ce1836c..ebd7983bd 100644 --- a/src/contracts/sessionToolContracts.ts +++ b/src/contracts/sessionToolContracts.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { isAbsoluteLocalPath } from "../domain/localPath.js"; import { sessionOutputSchemas, requireOutputSchema, @@ -47,7 +48,16 @@ const session = ( /** Session-owned Evidence bundle export options. */ export const exportEvidenceBundleInputSchema = z.strictObject({ - path: z.string().min(1), + path: z + .string() + .min(1) + .refine(isAbsoluteLocalPath, { + message: + "path must be an absolute local filesystem path (for example /tmp/rea/evidence.json or C:\\rea\\evidence.json)", + }) + .describe( + "Absolute local filesystem path for the exported evidence bundle; relative paths are rejected.", + ), overwrite: z.boolean().default(false), }); diff --git a/src/contracts/sessionToolSchemas.ts b/src/contracts/sessionToolSchemas.ts index 390709974..87afe287a 100644 --- a/src/contracts/sessionToolSchemas.ts +++ b/src/contracts/sessionToolSchemas.ts @@ -1,13 +1,15 @@ import { z } from "zod"; +import { isAbsoluteLocalPath } from "../domain/localPath.js"; + import { artifactComparisonInputSchema } from "../domain/artifactComparison.js"; import { bundleComparisonInputSchema } from "../domain/bundleComparison.js"; import { callPathInputSchema } from "../domain/callPath.js"; import { changedBehaviorInputSchema } from "../domain/changedBehavior.js"; import { functionComparisonInputSchema } from "../domain/functionComparison.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; -import { processScenarioSchema } from "../domain/processCapture.js"; -import { processTraceSpecificationSchema } from "../domain/processTraceComparison.js"; +import { processScenarioSchema } from "../domain/process/processCapture.js"; +import { processTraceSpecificationSchema } from "../domain/process/processTraceComparison.js"; import { recordUnknownInputSchema } from "../domain/residualUnknown.js"; import { reconstructionVerificationInputSchema } from "../domain/reconstructionVerification.js"; import { staticRuntimeCorrelationInputSchema } from "../domain/staticRuntimeCorrelation.js"; @@ -36,7 +38,16 @@ export const addressContextInputSchema = z.strictObject({ /** Session-owned Evidence bundle import options. */ export const importEvidenceBundleInputSchema = z.strictObject({ - path: z.string().min(1), + path: z + .string() + .min(1) + .refine(isAbsoluteLocalPath, { + message: + "path must be an absolute local filesystem path (for example /tmp/evidence.json or C:\\rea\\evidence.json)", + }) + .describe( + "Absolute local filesystem path for the evidence bundle to import; relative paths are rejected.", + ), }); /** Evidence references for deterministic process comparison. */ diff --git a/src/contracts/toolContractExamples.ts b/src/contracts/toolContractExamples.ts index 6a47936e2..2135a8543 100644 --- a/src/contracts/toolContractExamples.ts +++ b/src/contracts/toolContractExamples.ts @@ -55,7 +55,7 @@ export const TOOL_EXAMPLE_OVERRIDES: Readonly< open_binary: { path: "/tmp/fixture" }, export_evidence_bundle: { path: "/tmp/evidence.json" }, inspect_address_context: { address: "0x1000" }, - import_evidence_bundle: { path: "evidence.json" }, + import_evidence_bundle: { path: "/tmp/evidence.json" }, capture_process_scenario: { executable: "/usr/bin/true", working_directory: "/tmp", diff --git a/src/contracts/toolContracts.test.ts b/src/contracts/toolContracts.test.ts index 6fec3a226..449e5f785 100644 --- a/src/contracts/toolContracts.test.ts +++ b/src/contracts/toolContracts.test.ts @@ -17,6 +17,7 @@ import { APPLICATION_TOOL_CONTRACTS } from "./applicationToolContracts.js"; import { JAVASCRIPT_RECOVERY_TOOL_CONTRACTS } from "./javascript/javascriptRecoveryToolContracts.js"; import { WEB_SCRIPT_TOOL_CONTRACTS } from "./webScriptToolContracts.js"; import { WEB_RUNTIME_TOOL_CONTRACTS } from "./webRuntimeToolContracts.js"; +import { WEB_NETWORK_CAPTURE_TOOL_CONTRACTS } from "./webNetworkCaptureToolContracts.js"; const GROUPS = { official: OFFICIAL_TOOL_CONTRACTS, @@ -34,6 +35,7 @@ const GROUPS = { javascript_recovery: JAVASCRIPT_RECOVERY_TOOL_CONTRACTS, web_script_export: WEB_SCRIPT_TOOL_CONTRACTS, web_runtime: WEB_RUNTIME_TOOL_CONTRACTS, + web_network_capture: WEB_NETWORK_CAPTURE_TOOL_CONTRACTS, session: SESSION_TOOL_CONTRACTS, } as const; diff --git a/src/contracts/toolContracts.ts b/src/contracts/toolContracts.ts index dd3896c43..40ed7a6db 100644 --- a/src/contracts/toolContracts.ts +++ b/src/contracts/toolContracts.ts @@ -10,6 +10,7 @@ import { JAVASCRIPT_RUNTIME_OBSERVATION_TOOL_CONTRACTS } from "./javascript/java import { APPLICATION_TOOL_CONTRACTS } from "./applicationToolContracts.js"; import { WEB_SCRIPT_TOOL_CONTRACTS } from "./webScriptToolContracts.js"; import { WEB_RUNTIME_TOOL_CONTRACTS } from "./webRuntimeToolContracts.js"; +import { WEB_NETWORK_CAPTURE_TOOL_CONTRACTS } from "./webNetworkCaptureToolContracts.js"; import { JAVASCRIPT_RECOVERY_TOOL_CONTRACTS } from "./javascript/javascriptRecoveryToolContracts.js"; import { OFFICIAL_TOOL_CONTRACTS } from "./officialToolContracts.js"; import { ENHANCED_TOOL_CONTRACTS } from "./enhancedToolContracts.js"; @@ -33,6 +34,7 @@ export const TOOL_CONTRACTS = [ ...APPLICATION_TOOL_CONTRACTS, ...WEB_SCRIPT_TOOL_CONTRACTS, ...WEB_RUNTIME_TOOL_CONTRACTS, + ...WEB_NETWORK_CAPTURE_TOOL_CONTRACTS, ...JAVASCRIPT_RECOVERY_TOOL_CONTRACTS, ...SESSION_TOOL_CONTRACTS, ] as const; diff --git a/src/contracts/toolEffects.ts b/src/contracts/toolEffects.ts index 17864bf21..a5c30788d 100644 --- a/src/contracts/toolEffects.ts +++ b/src/contracts/toolEffects.ts @@ -41,6 +41,11 @@ const sessionEvidence = effects({ mutatesSession: true }); /** Explicit effect audit for every public tool. */ export const TOOL_EFFECTS: Readonly> = { + inspect_web_network_capture: effects({ + mutatesSession: true, + writesFilesystem: true, + launchesProcess: true, + }), observe_web_execution: effects({ mutatesTarget: true, mutatesSession: true, diff --git a/src/contracts/toolOutputSchemaGroups.ts b/src/contracts/toolOutputSchemaGroups.ts index 164fbc37e..12815d3b0 100644 --- a/src/contracts/toolOutputSchemaGroups.ts +++ b/src/contracts/toolOutputSchemaGroups.ts @@ -16,7 +16,7 @@ import { evidenceBundleSchema } from "../domain/evidenceBundle.js"; import { processCaptureComparisonSchema, processCaptureSchema, -} from "../domain/processCapture.js"; +} from "../domain/process/processCapture.js"; import { functionInstructionWindowSchema, referenceKindSchema, diff --git a/src/contracts/toolOutputSchemaPrimitives.ts b/src/contracts/toolOutputSchemaPrimitives.ts index bf4a4e25e..9e064c2c3 100644 --- a/src/contracts/toolOutputSchemaPrimitives.ts +++ b/src/contracts/toolOutputSchemaPrimitives.ts @@ -363,7 +363,7 @@ export const sessionProvider = z negotiated_protocol_version: z.string().nullable(), client: z.object({ name: z.string(), version: z.string() }).nullable(), skill: z.object({ name: z.string(), expected_version: z.string() }), - catalog: z.record(z.string(), z.json()), + catalog: z.record(z.string(), jsonValueSchema), protocol_features: z.object({ progress: z.boolean(), cancellation: z.boolean(), diff --git a/src/contracts/webNetworkCaptureToolContracts.ts b/src/contracts/webNetworkCaptureToolContracts.ts new file mode 100644 index 000000000..a9e45d606 --- /dev/null +++ b/src/contracts/webNetworkCaptureToolContracts.ts @@ -0,0 +1,30 @@ +import { + inspectWebNetworkCaptureInputSchema, + webNetworkCaptureSchema, +} from "../domain/webNetworkCapture.js"; +import type { ToolContract } from "./toolContracts.js"; +import { toolContractMetadata } from "./toolEffects.js"; +import { evidenceResultOf } from "./toolOutputSchemas.js"; + +/** Historical inspection is distinct from live browser capture and runtime authority. */ +export const WEB_NETWORK_CAPTURE_TOOL_CONTRACTS = [ + { + name: "inspect_web_network_capture", + ...toolContractMetadata("inspect_web_network_capture"), + description: + "Inspect retained local HAR 1.2 or native mitmproxy capture records without contacting recorded URLs. Returns original ordinals, pointers/byte ranges, preserved producer fields, precise numeric representations and exact exposed binary bytes inline. Excludes known transport credentials and explicitly marked values. Decodes every record before optional ordinal selection; malformed or oversized captures return no partial success. Duplicate HAR members are invalid; prototype-named __proto__ members are unsupported by the current shared JSON boundary. Uses pinned upstream parsers, owned temporary files and bounded offline processes. Native mitmproxy requires caller-supplied mitmdump 12.2.3 on Linux. Browser transaction IDs, runtime attribution and original bytes of HAR Unicode text remain unknown.", + kind: "browser-provider", + inputSchema: inspectWebNetworkCaptureInputSchema, + outputSchema: evidenceResultOf(webNetworkCaptureSchema), + examples: [ + { + title: "Inspect historical HAR entries", + input: { + capture_path: "/captures/session.har", + format: "har", + record_ordinals: [0, 2], + }, + }, + ], + }, +] as const satisfies readonly ToolContract[]; diff --git a/src/domain/analysisAccessDenied.test.ts b/src/domain/analysisAccessDenied.test.ts new file mode 100644 index 000000000..2f5ce5c43 --- /dev/null +++ b/src/domain/analysisAccessDenied.test.ts @@ -0,0 +1,29 @@ +import { expect, it } from "vitest"; +import { AnalysisAccessDeniedError } from "./analysisErrorCore.js"; +import { projectAnalysisError } from "./analysisErrorProjection.js"; +import { analysisErrorProjectionSchema } from "../contracts/errorSchemas.js"; + +it.each(["EACCES", "EPERM"] as const)( + "projects %s as host access denial with correction guidance and a valid public schema", + (systemCode) => { + const error = new AnalysisAccessDeniedError( + "inspect_web_network_capture", + "/selected/capture.har", + systemCode, + { cause: new Error("internal detail") }, + ); + const projected = projectAnalysisError(error); + expect(analysisErrorProjectionSchema.parse(projected)).toMatchObject({ + code: "access_denied", + category: "unavailable", + retryable: false, + details: { + path: "/selected/capture.har", + system_code: systemCode, + boundary: "filesystem-read", + }, + }); + expect(projected.remediation.action).toContain("read access"); + expect(JSON.stringify(projected)).not.toContain("internal detail"); + }, +); diff --git a/src/domain/analysisErrorBase.ts b/src/domain/analysisErrorBase.ts index 2c423fc3d..33db95a58 100644 --- a/src/domain/analysisErrorBase.ts +++ b/src/domain/analysisErrorBase.ts @@ -2,6 +2,8 @@ const ANALYSIS_ERROR_TAGS = [ "AnalysisProtocolError", "AnalysisInputError", + "AnalysisAccessDeniedError", + "AnalysisArtifactChangedError", "AnalysisOutputError", "AnalysisCapabilityUnavailableError", "AnalysisCancelledError", @@ -35,4 +37,12 @@ export abstract class AnalysisError extends Error { readonly userCategory: "cancelled" | undefined = undefined; readonly cleanupIncomplete: boolean = false; readonly cleanupResources: readonly string[] = []; + readonly executionFailure: string | undefined = undefined; + readonly partialObservation: PartialProcessCaptureObservation | undefined = + undefined; + readonly cleanupReport: ProcessCaptureCleanupReport | undefined = undefined; } +import type { + PartialProcessCaptureObservation, + ProcessCaptureCleanupReport, +} from "./process/processCapture.js"; diff --git a/src/domain/analysisErrorCore.ts b/src/domain/analysisErrorCore.ts index e709dabc2..dd89321f0 100644 --- a/src/domain/analysisErrorCore.ts +++ b/src/domain/analysisErrorCore.ts @@ -90,3 +90,32 @@ export class AnalysisTimeoutError extends AnalysisError { ); } } + +/** Host filesystem permissions denied the selected read; this is not malformed caller input. */ +export class AnalysisAccessDeniedError extends AnalysisError { + readonly _tag = "AnalysisAccessDeniedError"; + constructor( + readonly operation: string, + readonly path: string, + readonly systemCode: "EACCES" | "EPERM", + options?: ErrorOptions, + ) { + super( + `Host filesystem read access denied (${systemCode}) for ${path} during ${operation}`, + options, + ); + } +} + +/** Selected artifact acquisition observed a change, so no stable identity can be reported. */ +export class AnalysisArtifactChangedError extends AnalysisError { + readonly _tag = "AnalysisArtifactChangedError"; + constructor( + readonly operation: string, + readonly path: string, + readonly reason: string, + options?: ErrorOptions, + ) { + super(`Selected artifact changed during acquisition: ${reason}`, options); + } +} diff --git a/src/domain/analysisErrorPresentation.ts b/src/domain/analysisErrorPresentation.ts index faa7b3e8b..f4baf9f18 100644 --- a/src/domain/analysisErrorPresentation.ts +++ b/src/domain/analysisErrorPresentation.ts @@ -1,4 +1,8 @@ -import { AnalysisInputError } from "./analysisErrorCore.js"; +import { + AnalysisAccessDeniedError, + AnalysisArtifactChangedError, + AnalysisInputError, +} from "./analysisErrorCore.js"; import { ArtifactOperationError } from "./artifactOperationError.js"; import { BinaryTargetError, @@ -47,6 +51,10 @@ export const analysisErrorRemediationAction = ( error.constraint === "directory_requires_file" ) return "For a JavaScript/Electron application directory, call analyze_javascript_application with input_path or run `rea analyze `. For binary analysis, select its executable file."; + if (error instanceof AnalysisAccessDeniedError) + return "Check the current process's read access to the selected path. Retry with a readable local file."; + if (error instanceof AnalysisArtifactChangedError) + return "Wait until the selected file is stable, then retry this operation."; if (error instanceof AnalysisInputError) return "Correct the listed arguments and retry."; if (error instanceof UnknownRegistryError && error.reason === "not-found") @@ -107,6 +115,8 @@ const STATIC_ERROR_CATEGORIES: Readonly< Partial> > = { AnalysisInputError: "invalid_input", + AnalysisAccessDeniedError: "unavailable", + AnalysisArtifactChangedError: "integrity_mismatch", AnalysisCapabilityUnavailableError: "unsupported_provider", ProviderSelectionError: "unsupported_provider", EvidenceIntegrityError: "integrity_mismatch", @@ -119,6 +129,10 @@ const STATIC_ERROR_CATEGORIES: Readonly< }; export const analysisErrorUserMessage = (error: AnalysisError): string => { + if (error instanceof AnalysisAccessDeniedError) + return "Host filesystem permissions denied read access to the selected path."; + if (error instanceof AnalysisArtifactChangedError) + return "The selected artifact changed during acquisition; no stable snapshot was decoded."; if (error instanceof AnalysisInputError) return "Analysis input is invalid. Check the arguments and try again."; const hopperMessage = hopperErrorUserMessage(error); @@ -243,6 +257,8 @@ const evidenceFileMessage = (reason: EvidenceFileError["reason"]): string => { const KNOWN_ERROR_TAGS = { AnalysisProtocolError: true, AnalysisInputError: true, + AnalysisAccessDeniedError: true, + AnalysisArtifactChangedError: true, AnalysisOutputError: true, AnalysisCapabilityUnavailableError: true, AnalysisCancelledError: true, diff --git a/src/domain/analysisErrorProjection.ts b/src/domain/analysisErrorProjection.ts index f4e8b6197..2f61a932f 100644 --- a/src/domain/analysisErrorProjection.ts +++ b/src/domain/analysisErrorProjection.ts @@ -1,4 +1,4 @@ -import type { JsonValue } from "./jsonValue.js"; +import { jsonValueSchema, type JsonValue } from "./jsonValue.js"; import { analysisErrorCategory, @@ -7,6 +7,8 @@ import { assertKnownAnalysisErrorTag, } from "./analysisErrorPresentation.js"; import { + AnalysisAccessDeniedError, + AnalysisArtifactChangedError, AnalysisCancelledError, AnalysisCapabilityUnavailableError, AnalysisInputError, @@ -130,6 +132,8 @@ const STATIC_ERROR_CODES = { AnalysisOutputError: "unreadable_output", HopperProtocolError: "unreadable_output", AnalysisInputError: "invalid_request", + AnalysisAccessDeniedError: "access_denied", + AnalysisArtifactChangedError: "artifact_changed", AnalysisCapabilityUnavailableError: "capability_unavailable", AnalysisCancelledError: "cancelled", HopperCancelledError: "cancelled", @@ -177,6 +181,20 @@ const errorDetails = ( const requestErrorDetails = ( error: AnalysisError, ): Readonly> | undefined => { + if (error instanceof AnalysisArtifactChangedError) + return { + operation: error.operation, + path: error.path, + reason: error.reason, + boundary: "stable-artifact-read", + }; + if (error instanceof AnalysisAccessDeniedError) + return { + operation: error.operation, + path: error.path, + system_code: error.systemCode, + boundary: "filesystem-read", + }; if (error instanceof AnalysisOutputError) return { operation: error.operation, reason: error.reason }; if (error instanceof AnalysisInputError && error.issues.length > 0) @@ -336,12 +354,30 @@ const lifecycleErrorDetails = ( return { cleanup: "incomplete", resources: [...error.cleanupResources], + ...(error.cleanupReport === undefined + ? {} + : { cleanup_report: jsonValueSchema.parse(error.cleanupReport) }), + ...(error.executionFailure === undefined + ? {} + : { execution_failure: error.executionFailure }), + ...(error.partialObservation === undefined + ? {} + : { + partial_observation: jsonValueSchema.parse( + error.partialObservation, + ), + }), }; if ( error._tag === "ProcessCaptureError" && error.userCategory === "cancelled" ) return { operation: "process_capture", cleanup: "complete" }; + if ( + error._tag === "ProcessCaptureError" && + error.executionFailure !== undefined + ) + return { execution_failure: error.executionFailure }; if (error instanceof BinaryTargetError) return { path: error.path, @@ -354,6 +390,7 @@ const lifecycleErrorDetails = ( }; const RETRYABLE_CODES: ReadonlySet = new Set([ + "artifact_changed", "invalid_request", "provider_timeout", "cancelled", @@ -366,6 +403,8 @@ export interface AnalysisErrorProjection extends Readonly< > { readonly code: | "invalid_request" + | "access_denied" + | "artifact_changed" | "unreadable_output" | "capability_unavailable" | "provider_unavailable" diff --git a/src/domain/analysisSnapshot.test.ts b/src/domain/analysisSnapshot.test.ts index da6fd3a61..1ee8cc620 100644 --- a/src/domain/analysisSnapshot.test.ts +++ b/src/domain/analysisSnapshot.test.ts @@ -17,6 +17,55 @@ import { import { createEvidence } from "./evidence.js"; import { createEvidenceBundle } from "./evidenceBundle.js"; +const snapshotWithHistoricalEvidence = () => { + const target = snapshotTarget(ANALYSIS_SNAPSHOT_TARGET); + const binding = snapshotBinding(ANALYSIS_SNAPSHOT_PROFILE); + const parameters = { procedure: "main" }; + const makeEvidence = (result: string) => + createEvidence(ANALYSIS_SNAPSHOT_TARGET, ANALYSIS_SNAPSHOT_PROVIDER, { + operation: "analyze_function", + parameters, + result: { summary: result }, + analysisProfile: ANALYSIS_SNAPSHOT_PROFILE, + }); + const current = makeEvidence("cached"); + const entry = { + query_id: analysisQueryId(target, binding, "analyze_function", parameters), + operation: "analyze_function", + parameters, + execution: { + result: { summary: "cached" }, + raw_result: null, + provider: binding.provider, + limitations: [], + locations: [], + subject: { + path: ANALYSIS_SNAPSHOT_TARGET.path, + sha256: ANALYSIS_SNAPSHOT_TARGET.sha256, + format: ANALYSIS_SNAPSHOT_TARGET.format, + architecture: ANALYSIS_SNAPSHOT_TARGET.architecture ?? null, + }, + }, + }; + return { + current, + snapshot: { + target, + binding, + entries: [entry], + evidence_bundle: createEvidenceBundle([ + makeEvidence("older"), + current, + createEvidence(ANALYSIS_SNAPSHOT_TARGET, ANALYSIS_SNAPSHOT_PROVIDER, { + operation: "legacy_query", + parameters: {}, + result: { summary: "legacy" }, + }), + ]), + } satisfies AnalysisSnapshot, + }; +}; + describe("analysis snapshot contract", () => { it("preserves DOS MZ target and subject formats with the x86 family", () => { const target = { @@ -91,31 +140,7 @@ describe("analysis snapshot contract", () => { describe("analysis snapshot Evidence binding", () => { it("finds only Evidence committed to the exact binding and profile", () => { - const evidence = createEvidence( - ANALYSIS_SNAPSHOT_TARGET, - ANALYSIS_SNAPSHOT_PROVIDER, - { - operation: "analyze_function", - parameters: { procedure: "main" }, - result: { summary: "cached" }, - analysisProfile: ANALYSIS_SNAPSHOT_PROFILE, - }, - ); - const legacy = createEvidence( - ANALYSIS_SNAPSHOT_TARGET, - ANALYSIS_SNAPSHOT_PROVIDER, - { - operation: "legacy_query", - parameters: {}, - result: { summary: "legacy" }, - }, - ); - const snapshot: AnalysisSnapshot = { - target: snapshotTarget(ANALYSIS_SNAPSHOT_TARGET), - binding: snapshotBinding(ANALYSIS_SNAPSHOT_PROFILE), - entries: [], - evidence_bundle: createEvidenceBundle([evidence, legacy]), - }; + const { current, snapshot } = snapshotWithHistoricalEvidence(); expect( snapshotEvidenceForQuery(snapshot, { target: ANALYSIS_SNAPSHOT_TARGET, @@ -125,7 +150,7 @@ describe("analysis snapshot Evidence binding", () => { provider: ANALYSIS_SNAPSHOT_PROVIDER, evidenceProfile: ANALYSIS_SNAPSHOT_PROFILE, }), - ).toEqual(evidence); + ).toEqual(current); expect( snapshotEvidenceForQuery(snapshot, { target: ANALYSIS_SNAPSHOT_TARGET, diff --git a/src/domain/analysisSnapshot.ts b/src/domain/analysisSnapshot.ts index 459aa3720..c91144802 100644 --- a/src/domain/analysisSnapshot.ts +++ b/src/domain/analysisSnapshot.ts @@ -101,17 +101,33 @@ const entrySchema = z.object({ subject: subjectSchema.nullable(), }), }); +const workflowEntrySchema = z.object({ + query_id: prefixedDigestSchema("query"), + operation: z.string().min(1), + parameters: jsonObjectSchema, + execution: z.object({ + result: jsonValueSchema, + raw_result: jsonValueSchema.nullable(), + provider: committedProviderSchema, + analysis_profile: analysisProfileSchema, + limitations: z.array(z.string()), + locations: z.array(evidenceLocationSchema), + subject: subjectSchema.nullable(), + }), +}); /** Provider- and profile-exact cache of successful immutable analysis calls. */ export const analysisSnapshotSchema = z.object({ target: targetSchema, binding: bindingSchema, entries: z.array(entrySchema), + workflow_entries: z.array(workflowEntrySchema).optional(), evidence_bundle: evidenceBundleSchema, }); export type AnalysisSnapshot = z.infer; export type AnalysisSnapshotEntry = z.infer; +export type AnalysisSnapshotWorkflowEntry = z.infer; export type AnalysisSnapshotTarget = z.infer; export type AnalysisSnapshotBinding = z.infer; @@ -208,6 +224,19 @@ export const snapshotEvidenceForQuery = ( if (!snapshotMatchesBinding(snapshot, target, bindingProfile)) return undefined; const encodedParameters = canonicalJson(parameters); + const queryId = analysisQueryId( + snapshot.target, + snapshot.binding, + operation, + parameters, + ); + const entry = snapshot.entries.find( + (candidate) => candidate.query_id === queryId, + ); + const workflowEntry = snapshot.workflow_entries?.find( + (candidate) => candidate.query_id === queryId, + ); + if (entry === undefined && workflowEntry === undefined) return undefined; return snapshot.evidence_bundle.records.find( (record) => record.subject?.digest.sha256 === target.sha256 && @@ -217,10 +246,77 @@ export const snapshotEvidenceForQuery = ( record.provider.version === provider.version && "analysis_profile" in record && analysisProfilesEqual(record.analysis_profile, evidenceProfile) && - canonicalJson(record.parameters) === encodedParameters, + canonicalJson(record.parameters) === encodedParameters && + ((entry !== undefined && evidenceMatchesEntry(record, entry, snapshot)) || + (workflowEntry !== undefined && + evidenceMatchesWorkflowEntry(record, workflowEntry, snapshot))), ); }; +/** Create one exact replay record for a derived application workflow result. */ +export const createAnalysisSnapshotWorkflowEntry = (input: { + readonly target: AnalysisSnapshotTarget; + readonly binding: AnalysisSnapshotBinding; + readonly operation: string; + readonly parameters: Readonly>; + readonly execution: SnapshotExecution & { + readonly analysisProfile: AnalysisProfileCommitment; + }; +}): AnalysisSnapshotWorkflowEntry => { + const { target, binding, operation, parameters, execution } = input; + if ( + execution.provider.id !== execution.analysisProfile.provider.id || + execution.provider.name !== execution.analysisProfile.provider.name || + execution.provider.version !== execution.analysisProfile.provider.version + ) + throw new TypeError( + "Workflow snapshot execution provider does not match its profile", + ); + return { + query_id: analysisQueryId(target, binding, operation, parameters), + operation, + parameters: jsonObjectSchema.parse(parameters), + execution: { + result: execution.result, + raw_result: execution.rawResult, + provider: execution.analysisProfile.provider, + analysis_profile: execution.analysisProfile, + limitations: [...execution.limitations], + locations: [...execution.locations], + subject: + execution.subject === null + ? null + : { + ...execution.subject, + architecture: execution.subject.architecture ?? null, + }, + }, + }; +}; + +const evidenceMatchesEntry = ( + evidence: Evidence, + entry: AnalysisSnapshotEntry, + snapshot: Pick, +): boolean => + isCorrespondingEvidence(evidence, snapshot) && + canonicalJson({ + query: evidenceQueryKey(evidence), + normalized_result: evidence.normalized_result, + raw_result: evidence.raw_result, + limitations: evidence.limitations, + locations: evidence.locations, + subject: + evidence.subject === null + ? null + : { + local_path: evidence.subject.local_path, + sha256: evidence.subject.digest.sha256, + format: evidence.subject.format, + architecture: evidence.subject.architecture, + }, + }) === entryEvidenceKey(entry, snapshot.binding); + /** Compute the stable lookup key for one provider/profile-specific query. */ export const analysisQueryId = ( target: AnalysisSnapshotTarget, @@ -286,7 +382,9 @@ export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot => { ); const ids = new Set(); const boundEntries: AnalysisSnapshotEntry[] = []; + const boundWorkflowEntries: AnalysisSnapshotWorkflowEntry[] = []; const index = createEvidenceIndex(parsed); + const workflowIndex = createWorkflowEvidenceIndex(parsed); for (const entry of parsed.entries) { if ( entry.execution.provider.id !== parsed.binding.provider.id || @@ -316,12 +414,60 @@ export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot => { } boundEntries.push(entry); } + for (const entry of parsed.workflow_entries ?? []) { + if ( + entry.execution.provider.id !== + entry.execution.analysis_profile.provider.id || + entry.execution.provider.name !== + entry.execution.analysis_profile.provider.name || + entry.execution.provider.version !== + entry.execution.analysis_profile.provider.version + ) + throw new TypeError( + "Analysis snapshot workflow entry provider does not match its profile", + ); + const expected = analysisQueryId( + parsed.target, + parsed.binding, + entry.operation, + entry.parameters, + ); + if (entry.query_id !== expected) + throw new TypeError( + "Analysis snapshot workflow query identifier does not match", + ); + if (ids.has(entry.query_id)) + throw new TypeError("Analysis snapshot contains duplicate queries"); + ids.add(entry.query_id); + if (!workflowEntryHasMatchingEvidence(entry, workflowIndex)) { + if (workflowEntryHasCorrespondingEvidence(entry, workflowIndex)) + throw new TypeError( + `Analysis snapshot workflow entry ${entry.operation} differs from its Evidence record`, + ); + continue; + } + boundWorkflowEntries.push(entry); + } const sorted = [...parsed.entries].sort((left, right) => left.query_id.localeCompare(right.query_id), ); if (JSON.stringify(parsed.entries) !== JSON.stringify(sorted)) throw new TypeError("Analysis snapshot entries are not canonical"); - return { ...parsed, entries: boundEntries }; + const sortedWorkflows = [...(parsed.workflow_entries ?? [])].sort( + (left, right) => left.query_id.localeCompare(right.query_id), + ); + if ( + parsed.workflow_entries !== undefined && + JSON.stringify(parsed.workflow_entries) !== JSON.stringify(sortedWorkflows) + ) + throw new TypeError("Analysis snapshot workflow entries are not canonical"); + return { + ...parsed, + entries: boundEntries, + ...(parsed.workflow_entries === undefined + ? {} + : { workflow_entries: boundWorkflowEntries }), + }; }; /** Check that a cached provider execution is represented by bundled Evidence. */ @@ -429,6 +575,108 @@ const entryEvidenceKey = ( }, }); +const workflowEntryQueryKey = (entry: AnalysisSnapshotWorkflowEntry): string => + canonicalJson({ + operation: entry.operation, + parameters: entry.parameters, + provider: entry.execution.provider, + analysis_profile: entry.execution.analysis_profile, + }); + +const workflowEntryEvidenceKey = ( + entry: AnalysisSnapshotWorkflowEntry, +): string => + canonicalJson({ + query: workflowEntryQueryKey(entry), + normalized_result: entry.execution.result, + raw_result: entry.execution.raw_result, + limitations: entry.execution.limitations, + locations: entry.execution.locations, + subject: + entry.execution.subject === null + ? null + : { + local_path: entry.execution.subject.path, + sha256: entry.execution.subject.sha256, + format: entry.execution.subject.format, + architecture: entry.execution.subject.architecture, + }, + }); + +const isWorkflowEvidence = ( + evidence: Evidence, + snapshot: Pick, + entry: AnalysisSnapshotWorkflowEntry, +): boolean => + evidence.predicate_type === "rea.analysis" && + evidence.confidence === "derived" && + evidence.authority === "shipped-artifact" && + evidence.subject?.digest.sha256 === snapshot.target.sha256 && + "analysis_profile" in evidence && + analysisProfilesEqual( + evidence.analysis_profile, + entry.execution.analysis_profile, + ) && + evidence.provider.id === entry.execution.provider.id && + evidence.provider.name === entry.execution.provider.name && + evidence.provider.version === entry.execution.provider.version; + +const createWorkflowEvidenceIndex = ( + snapshot: Pick, +): EvidenceIndex => { + const matching = new Set(); + const corresponding = new Set(); + for (const evidence of snapshot.evidence_bundle.records) { + if ( + evidence.predicate_type !== "rea.analysis" || + evidence.confidence !== "derived" || + evidence.authority !== "shipped-artifact" || + evidence.subject?.digest.sha256 !== snapshot.target.sha256 || + !("analysis_profile" in evidence) + ) + continue; + corresponding.add(evidenceQueryKey(evidence)); + matching.add(canonicalJson(evidenceContentProjection(evidence))); + } + return { matching, corresponding }; +}; + +const workflowEntryHasMatchingEvidence = ( + entry: AnalysisSnapshotWorkflowEntry, + index: EvidenceIndex, +): boolean => index.matching.has(workflowEntryEvidenceKey(entry)); + +const workflowEntryHasCorrespondingEvidence = ( + entry: AnalysisSnapshotWorkflowEntry, + index: EvidenceIndex, +): boolean => index.corresponding.has(workflowEntryQueryKey(entry)); + +const evidenceMatchesWorkflowEntry = ( + evidence: Evidence, + entry: AnalysisSnapshotWorkflowEntry, + snapshot: Pick, +): boolean => + isWorkflowEvidence(evidence, snapshot, entry) && + canonicalJson(evidenceContentProjection(evidence)) === + workflowEntryEvidenceKey(entry); + +const evidenceContentProjection = (evidence: Evidence) => ({ + query: evidenceQueryKey(evidence), + normalized_result: evidence.normalized_result, + raw_result: evidence.raw_result, + limitations: evidence.limitations, + locations: evidence.locations, + subject: + evidence.subject === null + ? null + : { + local_path: evidence.subject.local_path, + sha256: evidence.subject.digest.sha256, + format: evidence.subject.format, + architecture: evidence.subject.architecture, + }, +}); + /** Serialize a validated snapshot with byte-stable canonical entry ordering. */ export const serializeAnalysisSnapshot = (snapshot: AnalysisSnapshot): string => `${canonicalJson(parseAnalysisSnapshot(snapshot))}\n`; diff --git a/src/domain/android/androidApplication.ts b/src/domain/android/androidApplication.ts index 0a48a8a98..55718a664 100644 --- a/src/domain/android/androidApplication.ts +++ b/src/domain/android/androidApplication.ts @@ -118,6 +118,7 @@ export const projectAndroidApplication = ( ? ["Source inventory pages are incomplete; absence is unknown."] : []), "Manifest, resource, signing, and bytecode semantics require a dedicated Android provider; this projection reports exact inventory paths and hashes only.", + "Runtime families are inferred from inventory formats and paths; filename suffixes do not establish valid DEX or JVM class bytes.", "Bridge candidates are path-based hypotheses, not decoded JNI declarations or observed runtime calls.", ]; const withoutId = { @@ -176,11 +177,17 @@ const runtimeFamilies = (all: readonly Component[]) => { const families = new Set< AndroidApplicationProjectionResult["runtime_families"][number] >(); - if (all.some(({ path, format }) => format === "dex" || path.endsWith(".dex"))) + if ( + all.some( + ({ path, format }) => + format === "dex" || path.toLowerCase().endsWith(".dex"), + ) + ) families.add("dalvik-art"); if ( all.some( - ({ path, format }) => format === "jvm-class" || path.endsWith(".class"), + ({ path, format }) => + format === "jvm-class" || path.toLowerCase().endsWith(".class"), ) ) families.add("java-kotlin"); diff --git a/src/domain/apple/interfaceBuilderGraph.ts b/src/domain/apple/interfaceBuilderGraph.ts index fbab95170..37bc094c7 100644 --- a/src/domain/apple/interfaceBuilderGraph.ts +++ b/src/domain/apple/interfaceBuilderGraph.ts @@ -202,53 +202,66 @@ export const buildInterfaceBuilderAnalysis = (input: { }; let hierarchyCount = 0; let hierarchyTruncated = false; - const visitHierarchy = (value: unknown, parentId: string): void => { - if (hierarchyCount >= input.limits.max_objects) { - truncated = true; - hierarchyTruncated = true; - return; - } - if (Array.isArray(value)) { - for (const child of value) visitHierarchy(child, parentId); - return; - } - if (typeof value !== "object" || value === null) return; - const item = record(value); - const objectId = firstString( - item.objectID, - item["object-id"], - item.id, - typeof item.archiveUID === "number" ? String(item.archiveUID) : null, - ); - let nextParent = parentId; - if (objectId !== null) { - const known = objectIds.has(objectId); - const childId = known - ? objectNodeId(objectId) - : `${prefix}unknown:${objectId}`; - if ( - !known && - !addNode({ - id: childId, - kind: "unknown", - name: firstString(item.label, item.name) ?? objectId, - location: null, - attributes: { interface_builder_object_id: objectId }, - evidence: evidenceFor(`hierarchy item ${objectId}`), - }) - ) + const visitHierarchy = ( + initial: unknown, + initialParentId: string, + ): void => { + const pending: { readonly value: unknown; readonly parentId: string }[] = + [{ value: initial, parentId: initialParentId }]; + while (pending.length > 0) { + if (hierarchyCount >= input.limits.max_objects) { + truncated = true; hierarchyTruncated = true; - connect( - `${prefix}hierarchy:${parentId}:${childId}`, - parentId, - childId, - "contains", - `hierarchy ${objectId}`, + return; + } + const entry = pending.pop(); + if (entry === undefined) break; + const { value, parentId } = entry; + if (Array.isArray(value)) { + for (let index = value.length - 1; index >= 0; index -= 1) { + const child = value[index]; + if (child !== undefined) pending.push({ value: child, parentId }); + } + continue; + } + if (typeof value !== "object" || value === null) continue; + const item = record(value); + const objectId = firstString( + item.objectID, + item["object-id"], + item.id, + typeof item.archiveUID === "number" ? String(item.archiveUID) : null, ); - nextParent = childId; - hierarchyCount += 1; + let nextParent = parentId; + if (objectId !== null) { + const known = objectIds.has(objectId); + const childId = known + ? objectNodeId(objectId) + : `${prefix}unknown:${objectId}`; + if ( + !known && + !addNode({ + id: childId, + kind: "unknown", + name: firstString(item.label, item.name) ?? objectId, + location: null, + attributes: { interface_builder_object_id: objectId }, + evidence: evidenceFor(`hierarchy item ${objectId}`), + }) + ) + hierarchyTruncated = true; + connect( + `${prefix}hierarchy:${parentId}:${childId}`, + parentId, + childId, + "contains", + `hierarchy ${objectId}`, + ); + nextParent = childId; + hierarchyCount += 1; + } + pending.push({ value: item.children, parentId: nextParent }); } - visitHierarchy(item.children, nextParent); }; for (const hierarchy of parsed.hierarchy) visitHierarchy(hierarchy, rootId); for (const [index, item] of parsed.connections diff --git a/src/domain/apple/interfaceBuilderKeyedArchive.ts b/src/domain/apple/interfaceBuilderKeyedArchive.ts index d1ee5c3ea..f354af8ad 100644 --- a/src/domain/apple/interfaceBuilderKeyedArchive.ts +++ b/src/domain/apple/interfaceBuilderKeyedArchive.ts @@ -196,39 +196,85 @@ const keyedArchiveTable = (objectTable: readonly unknown[]) => { }; const hierarchy = (start: unknown): unknown[] => { const visited = new Set(); - const node = ( + const root: unknown[] = []; + const pending: { readonly value: unknown; readonly output: unknown[] }[] = [ + { value: start, output: root }, + ]; + const appendNode = ( item: Record, uid: number | undefined, - depth: number, - ): unknown[] => { + output: unknown[], + ): void => { const objectId = firstString( authoredId(item), text(item.id), uid === undefined ? null : String(uid), ); - const children = Object.entries(item) - .filter(([key]) => HIERARCHY_KEY.test(key)) - .flatMap(([, child]) => visit(child, depth + 1)); - return objectId === null ? children : [{ objectID: objectId, children }]; + if (objectId === null) { + for (const [key, child] of Object.entries(item).reverse()) + if (HIERARCHY_KEY.test(key)) pending.push({ value: child, output }); + return; + } + const children: unknown[] = []; + output.push({ objectID: objectId, children }); + for (const [key, child] of Object.entries(item).reverse()) + if (HIERARCHY_KEY.test(key)) + pending.push({ value: child, output: children }); }; - const visit = (value: unknown, depth: number): unknown[] => { - if (depth > 32) return []; + while (pending.length > 0) { + const entry = pending.pop(); + if (entry === undefined) break; + const { value, output } = entry; const uid = archiveUid(value); if (uid !== undefined) { - if (visited.has(uid)) return []; + if (visited.has(uid)) continue; visited.add(uid); const target = objectTable[uid]; - if (Array.isArray(target)) return visit(target, depth + 1); - return typeof target === "object" && target !== null - ? node(record(target), uid, depth) - : []; + if (Array.isArray(target)) { + for (let index = target.length - 1; index >= 0; index -= 1) { + const child = target[index]; + if (child !== undefined) pending.push({ value: child, output }); + } + continue; + } + if (typeof target !== "object" || target === null) continue; + const item = record(target); + if (archivedArrayClass(item.$class)) { + const members = item["NS.objects"]; + if (Array.isArray(members)) + for (let index = members.length - 1; index >= 0; index -= 1) { + const child = members[index]; + if (child !== undefined) pending.push({ value: child, output }); + } + continue; + } + appendNode(item, uid, output); + continue; } - if (Array.isArray(value)) - return value.flatMap((item) => visit(item, depth + 1)); - if (typeof value !== "object" || value === null) return []; - return node(record(value), undefined, depth); - }; - return visit(start, 0); + if (Array.isArray(value)) { + for (let index = value.length - 1; index >= 0; index -= 1) { + const child = value[index]; + if (child !== undefined) pending.push({ value: child, output }); + } + } else if (typeof value === "object" && value !== null) { + appendNode(record(value), undefined, output); + } + } + return root; + }; + const archivedArrayClass = (classReference: unknown): boolean => { + const uid = archiveUid(classReference); + const descriptor = record( + uid === undefined ? classReference : objectTable[uid], + ); + const classNames = Array.isArray(descriptor.$classes) + ? descriptor.$classes.filter( + (name): name is string => typeof name === "string", + ) + : []; + return ( + classNames.includes("NSArray") || classNames.includes("NSMutableArray") + ); }; return { resolve, hierarchy }; }; diff --git a/src/domain/artifactPathSyntax.test.ts b/src/domain/artifactPathSyntax.test.ts index 7a31567ae..50eb86c4d 100644 --- a/src/domain/artifactPathSyntax.test.ts +++ b/src/domain/artifactPathSyntax.test.ts @@ -12,6 +12,11 @@ describe("artifact path syntax", () => { expect(hasScheme("https://example.test/app.js")).toBe(true); expect(hasScheme("file:///app.js")).toBe(true); expect(hasScheme("node:fs")).toBe(true); + expect(hasScheme("web3:app.js")).toBe(true); + expect(hasScheme("h323:user@example.test")).toBe(true); + expect(hasScheme("x1+custom.v2-test:asset")).toBe(true); + expect(hasScheme("3web:app.js")).toBe(false); + expect(hasScheme("./web3:app.js")).toBe(false); expect(hasScheme("./relative.js")).toBe(false); expect(hasScheme("/absolute.js")).toBe(false); expect(hasScheme("app.js")).toBe(false); diff --git a/src/domain/artifactPathSyntax.ts b/src/domain/artifactPathSyntax.ts index 46666dcfb..382856a4c 100644 --- a/src/domain/artifactPathSyntax.ts +++ b/src/domain/artifactPathSyntax.ts @@ -17,7 +17,7 @@ /** Whether a reference carries a URI scheme (`https:`, `file:`, …). */ export const hasScheme = (value: string): boolean => - /^[A-Za-z][A-Za-z+.-]*:/u.test(value); + /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value); /** Whether a reference is scheme-qualified or protocol-relative. */ export const looksExternal = (value: string): boolean => diff --git a/src/domain/browserObservation.ts b/src/domain/browserObservation.ts index 7be780969..b64972a32 100644 --- a/src/domain/browserObservation.ts +++ b/src/domain/browserObservation.ts @@ -94,7 +94,7 @@ const browserInput = { }; /** Public input for complete discovery, optionally filtered by exact origin. */ -export const listBrowserTargetsInputSchema = z.object({ +export const listBrowserTargetsInputSchema = z.strictObject({ ...browserInput, }); diff --git a/src/domain/browserObservationSchemas.ts b/src/domain/browserObservationSchemas.ts index 84d3349b8..70b53b4a7 100644 --- a/src/domain/browserObservationSchemas.ts +++ b/src/domain/browserObservationSchemas.ts @@ -114,6 +114,21 @@ const browserNetworkRequestSchema = z.object({ status: z.number().nullable(), mime_type: z.string().nullable(), encoded_data_length: z.number().min(0).nullable(), + redirects: z + .array( + z.object({ + url: z.string(), + response_url: z.string(), + method: z.string(), + resource_type: z.string().nullable(), + status: z.number().nullable(), + mime_type: z.string().nullable(), + encoded_data_length: z.number().min(0).nullable(), + request_timestamp: z.number().nullable(), + redirect_event_timestamp: z.number().nullable(), + }), + ) + .optional(), initiator: z.object({ type: z.string(), url: z.string().nullable(), diff --git a/src/domain/browserScenario.test.ts b/src/domain/browserScenario.test.ts index c69beeaef..a5527c4a1 100644 --- a/src/domain/browserScenario.test.ts +++ b/src/domain/browserScenario.test.ts @@ -56,7 +56,7 @@ it("does not require duplicate origin scope declarations", () => { const scenario = browserScenarioSchema.parse({ browser: { mode: "launch", - executable_path: "chrome", + executable_path: "/opt/chromium/chrome", }, start_url: { url: "https://app.example.test/" }, actions: [ diff --git a/src/domain/browserScenarioValues.ts b/src/domain/browserScenarioValues.ts index a2cf1b97c..6313c7ca9 100644 --- a/src/domain/browserScenarioValues.ts +++ b/src/domain/browserScenarioValues.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { isAbsoluteLocalPath } from "./localPath.js"; import { browserNetworkContentSelectionSchema } from "./browserNetworkEvidence.js"; import { @@ -11,7 +12,14 @@ export const scenarioIdentifierSchema = z .string() .regex(/^[A-Za-z][A-Za-z0-9._-]*$/u); -const browserExecutablePathSchema = z.string().trim().min(1); +const browserExecutablePathSchema = z + .string() + .trim() + .min(1) + .refine(isAbsoluteLocalPath, { + message: + "executable_path must be an absolute local filesystem path (for example /opt/chromium/chrome or C:\\chromium\\chrome.exe)", + }); const browserScenarioBaseUrlSchema = z .string() @@ -67,7 +75,9 @@ export type BrowserScenarioUrl = z.infer; export const browserScenarioBrowserSchema = z.discriminatedUnion("mode", [ z.strictObject({ mode: z.literal("launch"), - executable_path: browserExecutablePathSchema, + executable_path: browserExecutablePathSchema.describe( + "Absolute local filesystem path for the browser executable; relative paths are rejected.", + ), headless: z .boolean() .default(true) diff --git a/src/domain/bytecodeProvider.test.ts b/src/domain/bytecodeProvider.test.ts deleted file mode 100644 index d70557521..000000000 --- a/src/domain/bytecodeProvider.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - analyzeBytecodeArtifacts, - bytecodeAnalysisSchema, - classifyProvenance, - detectArtifactFormat, - detectBytecodeFamily, - type BytecodeArtifact, -} from "./bytecodeProvider.js"; - -describe("bytecode provider", () => { - it("detects JVM family from extensions", () => { - expect(detectBytecodeFamily("App.class")).toBe("jvm"); - expect(detectBytecodeFamily("app.jar")).toBe("jvm"); - expect(detectBytecodeFamily("webapp.war")).toBe("jvm"); - expect(detectBytecodeFamily("app.ear")).toBe("jvm"); - }); - - it("detects Python family from extensions", () => { - expect(detectBytecodeFamily("module.pyc")).toBe("python"); - expect(detectBytecodeFamily("module.pyo")).toBe("python"); - expect(detectBytecodeFamily("package.whl")).toBe("python"); - expect(detectBytecodeFamily("app.pyz")).toBe("python"); - expect(detectBytecodeFamily("app.zipapp")).toBe("python"); - }); - - it("returns null for unknown extensions", () => { - expect(detectBytecodeFamily("readme.txt")).toBeNull(); - expect(detectBytecodeFamily("script.js")).toBeNull(); - }); - - it("detects artifact format from path", () => { - expect(detectArtifactFormat("App.class")).toBe("class"); - expect(detectArtifactFormat("lib.jar")).toBe("jar"); - expect(detectArtifactFormat("web.war")).toBe("war"); - expect(detectArtifactFormat("mod.pyc")).toBe("pyc"); - expect(detectArtifactFormat("pkg.whl")).toBe("whl"); - expect(detectArtifactFormat("unknown.xyz")).toBeNull(); - }); - - it("classifies standard library provenance", () => { - expect(classifyProvenance("/usr/lib/stdlib", "java.lang.String")).toBe( - "standard_library", - ); - expect( - classifyProvenance("/lib/python3.10/site-packages/numpy", "__init__"), - ).toBe("standard_library"); - }); - - it("classifies generated provenance", () => { - expect(classifyProvenance("/build/generated/src", "GeneratedClass")).toBe( - "generated", - ); - expect( - classifyProvenance("/__pycache__/module.cpython-310.pyc", "module"), - ).toBe("generated"); - }); - - it("classifies vendored provenance", () => { - expect(classifyProvenance("/vendor/libs/lib.jar", "VendorLib")).toBe( - "vendored", - ); - expect(classifyProvenance("/third_party/pkg/module.pyc", "module")).toBe( - "vendored", - ); - }); - - it("classifies application provenance", () => { - expect(classifyProvenance("/src/main/App.java", "com.example.App")).toBe( - "application", - ); - expect(classifyProvenance("/src/app.py", "main")).toBe("application"); - }); - - it("analyzes bytecode artifacts and computes summary", () => { - const artifacts: BytecodeArtifact[] = [ - { - path: "src/App.class", - family: "jvm", - format: "class", - size: 1000, - digest: "a".repeat(64), - symbols: [ - { - name: "com.example.App", - raw_location: "0x1000", - kind: "class", - bytecode_version: 52, - provenance: "application", - is_public: true, - }, - { - name: "java.lang.String", - raw_location: "0x2000", - kind: "class", - bytecode_version: 52, - provenance: "standard_library", - is_public: true, - }, - ], - is_standard_library: false, - is_generated: false, - is_vendored: false, - }, - { - path: "lib/generated/Generated.class", - family: "jvm", - format: "class", - size: 500, - digest: "b".repeat(64), - symbols: [ - { - name: "GeneratedClass", - raw_location: "0x3000", - kind: "class", - bytecode_version: 52, - provenance: "generated", - is_public: false, - }, - ], - is_standard_library: false, - is_generated: true, - is_vendored: false, - }, - ]; - const result = analyzeBytecodeArtifacts(artifacts, "jvm"); - expect(result.family).toBe("jvm"); - expect(result.total_symbols).toBe(3); - expect(result.application_symbols).toBe(1); - expect(result.standard_library_symbols).toBe(1); - expect(result.generated_symbols).toBe(1); - expect(result.vendored_symbols).toBe(0); - - // Validate schema - const parsed = bytecodeAnalysisSchema.safeParse(result); - expect(parsed.success).toBe(true); - }); - - it("returns every artifact when the inventory exceeds ten thousand entries", () => { - const artifacts = Array.from({ length: 10_001 }, (_, index) => ({ - path: `classes/${index}.class`, - family: "jvm" as const, - format: "class" as const, - size: 1, - digest: "a".repeat(64), - symbols: [], - is_standard_library: false, - is_generated: false, - is_vendored: false, - })); - - const result = analyzeBytecodeArtifacts(artifacts, "jvm"); - - expect(result.artifacts).toHaveLength(artifacts.length); - expect(bytecodeAnalysisSchema.parse(result).artifacts).toHaveLength( - artifacts.length, - ); - }); -}); diff --git a/src/domain/bytecodeProvider.ts b/src/domain/bytecodeProvider.ts deleted file mode 100644 index cdb56518e..000000000 --- a/src/domain/bytecodeProvider.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { z } from "zod"; - -/** Supported managed-runtime bytecode families. */ -export const bytecodeFamilySchema = z.enum(["jvm", "python"]); -export type BytecodeFamily = z.infer; - -/** Classification of bytecode provenance. */ -export const bytecodeProvenanceSchema = z.enum([ - "application", - "generated", - "vendored", - "bundled", - "standard_library", -]); -export type BytecodeProvenance = z.infer; - -/** A bytecode symbol discovered through static analysis. */ -export const bytecodeSymbolSchema = z.strictObject({ - /** Fully qualified name. */ - name: z.string().min(1), - /** Raw bytecode location or offset. */ - raw_location: z.string().min(1), - /** Normalized symbol type. */ - kind: z.enum([ - "class", - "interface", - "method", - "field", - "function", - "module", - "variable", - ]), - /** Bytecode version if known. */ - bytecode_version: z.number().int().nullable(), - /** Provenance classification. */ - provenance: bytecodeProvenanceSchema, - /** Whether the symbol is accessible (public). */ - is_public: z.boolean().default(false), -}); - -/** A discovered bytecode artifact (class file, wheel, etc). */ -export const bytecodeArtifactSchema = z.strictObject({ - /** Artifact path relative to the analysis root. */ - path: z.string().min(1), - /** Bytecode family. */ - family: bytecodeFamilySchema, - /** Artifact format. */ - format: z.enum(["class", "jar", "war", "ear", "pyc", "pyo", "whl", "zipapp"]), - /** Size in bytes. */ - size: z.number().int().nonnegative(), - /** SHA-256 digest. */ - digest: z.string().regex(/^[a-f0-9]{64}$/u), - /** Discovered symbols. */ - symbols: z.array(bytecodeSymbolSchema).default([]), - /** Whether this is a standard library artifact. */ - is_standard_library: z.boolean().default(false), - /** Whether this is a generated artifact. */ - is_generated: z.boolean().default(false), - /** Whether this is a vendored artifact. */ - is_vendored: z.boolean().default(false), -}); -export type BytecodeArtifact = z.infer; - -/** Static bytecode analysis result. */ -export const bytecodeAnalysisSchema = z.strictObject({ - /** Bytecode family. */ - family: bytecodeFamilySchema, - /** All discovered artifacts. */ - artifacts: z.array(bytecodeArtifactSchema), - /** Total symbols across all artifacts. */ - total_symbols: z.number().int().nonnegative(), - /** Symbols classified as application code. */ - application_symbols: z.number().int().nonnegative(), - /** Symbols classified as generated code. */ - generated_symbols: z.number().int().nonnegative(), - /** Symbols classified as vendored code. */ - vendored_symbols: z.number().int().nonnegative(), - /** Symbols classified as standard library. */ - standard_library_symbols: z.number().int().nonnegative(), -}); -export type BytecodeAnalysis = z.infer; - -/** Detect bytecode family from file extension. */ -export function detectBytecodeFamily(path: string): BytecodeFamily | null { - const lower = path.toLowerCase(); - if ( - lower.endsWith(".class") || - lower.endsWith(".jar") || - lower.endsWith(".war") || - lower.endsWith(".ear") - ) { - return "jvm"; - } - if ( - lower.endsWith(".pyc") || - lower.endsWith(".pyo") || - lower.endsWith(".whl") || - lower.endsWith(".pyz") || - lower.endsWith(".zipapp") - ) { - return "python"; - } - return null; -} - -/** Detect artifact format from file path. */ -export function detectArtifactFormat( - path: string, -): `class` | `jar` | `war` | `ear` | `pyc` | `pyo` | `whl` | `zipapp` | null { - const lower = path.toLowerCase(); - if (lower.endsWith(".class")) return "class"; - if (lower.endsWith(".jar")) return "jar"; - if (lower.endsWith(".war")) return "war"; - if (lower.endsWith(".ear")) return "ear"; - if (lower.endsWith(".pyc")) return "pyc"; - if (lower.endsWith(".pyo")) return "pyo"; - if (lower.endsWith(".whl")) return "whl"; - if (lower.endsWith(".pyz") || lower.endsWith(".zipapp")) return "zipapp"; - return null; -} - -const STANDARD_LIBRARY_PATH_MARKERS = [ - "/stdlib/", - "/site-packages/", - "java/", - "javax/", - "sun/", -]; -const STANDARD_LIBRARY_SYMBOL_MARKERS = ["java.", "javax.", "sun.", "__"]; -const GENERATED_PATH_MARKERS = [ - "/generated/", - "_generated", - "/build/", - "/target/", - "/dist-packages/", - "/__pycache__/", -]; -const GENERATED_SYMBOL_MARKERS = ["Generated", "_generated"]; -const VENDORED_PATH_MARKERS = [ - "/vendor/", - "/vendored/", - "/third_party/", - "/3rdparty/", - "/node_modules/", -]; -const BUNDLED_PATH_MARKERS = ["/bundle/", "/bundled/", ".jar!", ".war!"]; - -const includesAny = (value: string, markers: readonly string[]): boolean => - markers.some((marker) => value.includes(marker)); -const startsWithAny = (value: string, markers: readonly string[]): boolean => - markers.some((marker) => value.startsWith(marker)); - -/** Classify bytecode symbol provenance. */ -export function classifyProvenance( - path: string, - symbolName: string, -): BytecodeProvenance { - const lower = path.toLowerCase(); - if ( - includesAny(lower, STANDARD_LIBRARY_PATH_MARKERS) || - startsWithAny(symbolName, STANDARD_LIBRARY_SYMBOL_MARKERS) - ) - return "standard_library"; - if ( - includesAny(lower, GENERATED_PATH_MARKERS) || - includesAny(symbolName, GENERATED_SYMBOL_MARKERS) - ) - return "generated"; - if (includesAny(lower, VENDORED_PATH_MARKERS)) return "vendored"; - if (includesAny(lower, BUNDLED_PATH_MARKERS)) return "bundled"; - return "application"; -} - -/** Analyze a set of bytecode artifacts and produce a summary. */ -export function analyzeBytecodeArtifacts( - artifacts: readonly BytecodeArtifact[], - family: BytecodeFamily, -): BytecodeAnalysis { - const totals = artifacts.reduce( - (counts, artifact) => { - counts.total += artifact.symbols.length; - for (const symbol of artifact.symbols) counts[symbol.provenance] += 1; - return counts; - }, - { - total: 0, - application: 0, - generated: 0, - vendored: 0, - standard_library: 0, - bundled: 0, - }, - ); - - return { - family, - artifacts: [...artifacts], - total_symbols: totals.total, - application_symbols: totals.application, - generated_symbols: totals.generated, - vendored_symbols: totals.vendored, - standard_library_symbols: totals.standard_library, - }; -} diff --git a/src/domain/callPathSchemas.ts b/src/domain/callPathSchemas.ts index 2d964f78f..130b7177c 100644 --- a/src/domain/callPathSchemas.ts +++ b/src/domain/callPathSchemas.ts @@ -24,7 +24,7 @@ export const parseCallPathAddress = (input: unknown): string => inputAddressSchema.parse(input); /** Strict input for explicit call-path reconstruction from complete dossiers. */ -export const callPathInputSchema = z.object({ +export const callPathInputSchema = z.strictObject({ functions: z.array(evidenceSchema).min(1), start: z.object({ address: inputAddressSchema }).strict(), goal: z.object({ address: inputAddressSchema }).strict(), diff --git a/src/domain/canonicalDigest.test.ts b/src/domain/canonicalDigest.test.ts index 02a641905..51df94619 100644 --- a/src/domain/canonicalDigest.test.ts +++ b/src/domain/canonicalDigest.test.ts @@ -62,6 +62,13 @@ describe("incremental canonical digest", () => { expect(digestCanonicalValue(value)).toBe(legacyDigest(value)); }); + it("keeps digests identical when buffered parts cross flush boundaries", () => { + const manyKeys: Record = {}; + for (let i = 0; i < 2000; i += 1) manyKeys[`k${i}`] = i; + const value = { keys: manyKeys, tail: ["€", "😀", "x".repeat(9000)] }; + expect(digestCanonicalValue(value)).toBe(legacyDigest(value)); + }); + it("preserves canonicalize property access and toJSON cycle behavior", () => { const createAccessor = () => { let reads = 0; diff --git a/src/domain/canonicalDigest.ts b/src/domain/canonicalDigest.ts index c9f1c6260..3c4f9e3c6 100644 --- a/src/domain/canonicalDigest.ts +++ b/src/domain/canonicalDigest.ts @@ -6,8 +6,19 @@ export const digestCanonicalValue = ( context = "Comparison", ): string => { const hash = createHash("sha256"); - const encoded = emitCanonical(value, (part) => hash.update(part), new Set()); + // Batch small emitted parts to cut hash.update crossings. + // Flush cuts only at part boundaries, so the hashed byte stream is unchanged. + let buffered = ""; + const emit = (part: string): void => { + buffered += part; + if (buffered.length >= 8192) { + hash.update(buffered); + buffered = ""; + } + }; + const encoded = emitCanonical(value, emit, new Set()); if (!encoded) throw new TypeError(`${context} could not canonicalize data`); + if (buffered) hash.update(buffered); return hash.digest("hex"); }; diff --git a/src/domain/changedBehavior.ts b/src/domain/changedBehavior.ts index ba46372b9..37cdd97ac 100644 --- a/src/domain/changedBehavior.ts +++ b/src/domain/changedBehavior.ts @@ -7,19 +7,19 @@ import { functionComparisonResultSchema } from "./functionComparison.js"; import { PROCESS_PROVIDER, isProcessEvidenceProvider, -} from "./processEvidenceProvider.js"; +} from "./process/processEvidenceProvider.js"; import { comparisonStatusSchema, deriveProcessComparisonStatus, PROCESS_COMPARISON_DIMENSIONS, processCaptureComparisonSchema, -} from "./processCapture.js"; +} from "./process/processCapture.js"; import { prefixedDigestSchema } from "./../domain/digests.js"; const evidenceIdSchema = prefixedDigestSchema("ev"); /** Input for aggregating existing comparison Evidence. */ -export const changedBehaviorInputSchema = z.object({ +export const changedBehaviorInputSchema = z.strictObject({ comparisons: z.array(evidenceSchema).default([]), }); diff --git a/src/domain/comparisonSemantics.test.ts b/src/domain/comparisonSemantics.test.ts index 9e86c0254..a2c569f6a 100644 --- a/src/domain/comparisonSemantics.test.ts +++ b/src/domain/comparisonSemantics.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { absenceClaimable, canonicalDigest } from "./comparisonSemantics.js"; +import { absenceClaimable } from "./comparisonSemantics.js"; describe("shared comparison semantics", () => { it("permits absence claims only for complete, non-truncated inventories", () => { @@ -27,10 +27,4 @@ describe("shared comparison semantics", () => { ).toBe(false); expect(absenceClaimable({ status: "partial" })).toBe(false); }); - - it("produces one stable SHA-256 for equivalent canonical JSON values", () => { - expect(canonicalDigest({ b: 2, a: 1 })).toBe( - canonicalDigest({ a: 1, b: 2 }), - ); - }); }); diff --git a/src/domain/comparisonStatus.test.ts b/src/domain/comparisonStatus.test.ts deleted file mode 100644 index a3b8b2e83..000000000 --- a/src/domain/comparisonStatus.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - bundleClassificationToCanonical, - comparisonConclusionSchema, - comparisonStatusSchema, - functionStatusToCanonical, - scenarioArtifactStatusToCanonical, - sourceToBundleStatusToCanonical, -} from "./comparisonStatus.js"; - -describe("comparison status vocabulary", () => { - it("owns the canonical unchanged/changed/unknown triple", () => { - expect(comparisonStatusSchema.options).toEqual([ - "unchanged", - "changed", - "unknown", - ]); - expect(comparisonConclusionSchema.options).toEqual([ - "observed_change", - "derived_relationship", - "contradiction", - "unresolved_branch", - ]); - }); - - it("projects every bundle classification", () => { - expect( - ( - [ - "added", - "removed", - "changed", - "unknown", - "history_advanced", - "history_diverged", - ] as const - ).map(bundleClassificationToCanonical), - ).toEqual([ - "changed", - "changed", - "changed", - "unknown", - "changed", - "changed", - ]); - }); - - it("projects every source-to-bundle item status", () => { - expect( - ( - [ - "unchanged", - "modified", - "removed", - "split", - "merged", - "duplicated", - "unknown", - ] as const - ).map(sourceToBundleStatusToCanonical), - ).toEqual([ - "unchanged", - "changed", - "changed", - "changed", - "changed", - "changed", - "unknown", - ]); - }); - - it("projects every function dimension status without claiming truncated work", () => { - expect( - (["unchanged", "changed", "truncated", "unknown"] as const).map( - functionStatusToCanonical, - ), - ).toEqual(["unchanged", "changed", "unknown", "unknown"]); - }); - - it("projects every scenario artifact status", () => { - expect( - (["changed", "unknown"] as const).map(scenarioArtifactStatusToCanonical), - ).toEqual(["changed", "unknown"]); - }); -}); diff --git a/src/domain/comparisonStatus.ts b/src/domain/comparisonStatus.ts index 2674c24c8..b23c90b1c 100644 --- a/src/domain/comparisonStatus.ts +++ b/src/domain/comparisonStatus.ts @@ -2,69 +2,11 @@ import { z } from "zod"; /** * Canonical top-level comparison outcome shared by every comparison - * pipeline. Pipeline-native detail enums (bundle classifications, - * source-to-bundle split/merge kinds, scenario alignment states, function - * truncation) keep their wire schemas; the mappers below project each of - * them onto this triple without changing any pipeline result. + * pipeline. Pipeline-native detail enums keep their wire schemas, while + * shared result contracts use this canonical triple. */ export const comparisonStatusSchema = z.enum([ "unchanged", "changed", "unknown", ]); - -export type ComparisonStatus = z.infer; - -/** Canonical comparison conclusion shared by every comparison pipeline. */ -export const comparisonConclusionSchema = z.enum([ - "observed_change", - "derived_relationship", - "contradiction", - "unresolved_branch", -]); - -/** Project a bundle change classification onto the canonical triple. */ -export const bundleClassificationToCanonical = ( - classification: - | "added" - | "removed" - | "changed" - | "unknown" - | "history_advanced" - | "history_diverged", -): ComparisonStatus => (classification === "unknown" ? "unknown" : "changed"); - -/** Project a source-to-bundle item status onto the canonical triple. */ -export const sourceToBundleStatusToCanonical = ( - status: - | "unchanged" - | "modified" - | "removed" - | "split" - | "merged" - | "duplicated" - | "unknown", -): ComparisonStatus => - status === "unchanged" - ? "unchanged" - : status === "unknown" - ? "unknown" - : "changed"; - -/** - * Project a function dimension status onto the canonical triple. A - * truncated dimension cannot support an unchanged/changed claim. - */ -export const functionStatusToCanonical = ( - status: "unchanged" | "changed" | "truncated" | "unknown", -): ComparisonStatus => - status === "unchanged" - ? "unchanged" - : status === "changed" - ? "changed" - : "unknown"; - -/** Project a browser-scenario artifact status onto the canonical triple. */ -export const scenarioArtifactStatusToCanonical = ( - status: "changed" | "unknown", -): ComparisonStatus => status; diff --git a/src/domain/conformanceTrustGate.ts b/src/domain/conformanceTrustGate.ts index 0b3e02b1c..14116f949 100644 --- a/src/domain/conformanceTrustGate.ts +++ b/src/domain/conformanceTrustGate.ts @@ -51,19 +51,6 @@ const VOLATILE_DIMENSIONS = new Set([ "updated_at", ]); -/** Dimension that carries semantic content. */ -const SEMANTIC_DIMENSIONS = new Set([ - "exit_code", - "stdout", - "stderr", - "filesystem", - "process", - "process_tree", - "shim_events", - "protocol_events", - "event_journal", -]); - type IncompleteStatus = "unknown" | "truncated"; type EvidenceView = { @@ -214,11 +201,6 @@ export function isVolatileDimension(name: string): boolean { return VOLATILE_DIMENSIONS.has(name); } -/** Check if a dimension name carries semantic content. */ -export function isSemanticDimension(name: string): boolean { - return SEMANTIC_DIMENSIONS.has(name); -} - /** * Compare two values for a dimension, classifying semantic diffs * from timing/normalization noise. diff --git a/src/domain/customProtocolCapture.test.ts b/src/domain/customProtocolCapture.test.ts deleted file mode 100644 index b39b880ca..000000000 --- a/src/domain/customProtocolCapture.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { fc } from "@fast-check/vitest"; -import { describe, expect, it } from "vitest"; - -import { - authEventsByProtocol, - authFlowDuration, - customProtocolCaptureSchema, - correlateProcessIdentity, - failedAuthEvents, - framesByTransport, - looksLikeCredentialFrame, - successfulAuthEvents, - type AuthFlowEvent, - type ProtocolFrame, -} from "./customProtocolCapture.js"; - -const sampleFrames: ProtocolFrame[] = [ - { - sequence: 0, - at_ms: 100, - transport: "tcp", - direction: "sent", - source_pid: 100, - dest_pid: 200, - source_endpoint: "127.0.0.1:8080", - dest_endpoint: "127.0.0.1:9090", - raw_data: "hello", - decoded_content: "hello", - size: 5, - truncated: false, - }, - { - sequence: 1, - at_ms: 200, - transport: "udp", - direction: "received", - source_pid: 200, - dest_pid: 100, - source_endpoint: "127.0.0.1:9090", - dest_endpoint: "127.0.0.1:8080", - raw_data: "world", - decoded_content: "world", - size: 5, - truncated: false, - }, -]; - -const sampleAuthEvents: AuthFlowEvent[] = [ - { - sequence: 0, - at_ms: 100, - stage: "init", - protocol: "OAuth2", - token_type: "Bearer", - token_lifecycle: "issued", - correlation_id: "abc-123", - credentials_redacted: true, - succeeded: false, - error: null, - }, - { - sequence: 1, - at_ms: 500, - stage: "success", - protocol: "OAuth2", - token_type: "Bearer", - token_lifecycle: "issued", - correlation_id: "abc-123", - credentials_redacted: true, - succeeded: true, - error: null, - }, -]; - -describe("custom protocol capture", () => { - it("rejects a capture with an unknown transport", () => { - const result = customProtocolCaptureSchema.safeParse({ - transport: "carrier-pigeon", - frames: sampleFrames, - auth_events: sampleAuthEvents, - has_truncated: false, - credentials_detected: false, - }); - expect(result.success).toBe(false); - }); - - it("retains every captured frame at any size", () => { - fc.assert( - fc.property( - // The bound must sit well above any ceiling a future change would - // plausibly introduce. A generated length of 40 would pass unnoticed - // against a cap at 100, so the generated arrays cross 1,000. - fc.array(fc.constant(sampleFrames[0]!), { - minLength: 300, - maxLength: 1_000, - }), - (frames) => { - const parsed = customProtocolCaptureSchema.parse({ - transport: "tcp", - frames: frames.map((frame, sequence) => ({ ...frame, sequence })), - }); - expect(parsed.frames).toHaveLength(frames.length); - }, - ), - { numRuns: 25 }, - ); - }); - - it("correlates process identity", () => { - expect(correlateProcessIdentity(sampleFrames[0]!, 100)).toBe(true); - expect(correlateProcessIdentity(sampleFrames[0]!, 200)).toBe(true); - expect(correlateProcessIdentity(sampleFrames[0]!, 999)).toBe(false); - }); - - it("filters frames by transport", () => { - const tcpFrames = framesByTransport(sampleFrames, "tcp"); - expect(tcpFrames).toHaveLength(1); - const udpFrames = framesByTransport(sampleFrames, "udp"); - expect(udpFrames).toHaveLength(1); - }); - - it("detects credential-like frames", () => { - const credFrame: ProtocolFrame = { - ...sampleFrames[0]!, - decoded_content: { password: "secret" }, - }; - expect(looksLikeCredentialFrame(credFrame)).toBe(true); - expect(looksLikeCredentialFrame(sampleFrames[0]!)).toBe(false); - }); - - it("filters auth events by protocol", () => { - const oauthEvents = authEventsByProtocol(sampleAuthEvents, "OAuth2"); - expect(oauthEvents).toHaveLength(2); - const otherEvents = authEventsByProtocol(sampleAuthEvents, "SAML"); - expect(otherEvents).toHaveLength(0); - }); - - it("gets successful auth events", () => { - const success = successfulAuthEvents(sampleAuthEvents); - expect(success).toHaveLength(1); - expect(success[0]!.succeeded).toBe(true); - }); - - it("gets failed auth events", () => { - const failed = failedAuthEvents(sampleAuthEvents); - expect(failed).toHaveLength(1); - expect(failed[0]!.succeeded).toBe(false); - }); - - it("computes auth flow duration", () => { - const duration = authFlowDuration(sampleAuthEvents); - expect(duration).toBe(400); - }); - - it("returns null for empty auth events", () => { - expect(authFlowDuration([])).toBeNull(); - }); -}); diff --git a/src/domain/customProtocolCapture.ts b/src/domain/customProtocolCapture.ts deleted file mode 100644 index 0b0989237..000000000 --- a/src/domain/customProtocolCapture.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { z } from "zod"; - -import { jsonValueSchema } from "./jsonValue.js"; - -/** Supported transport types for custom protocol capture. */ -export const transportTypeSchema = z.enum([ - "tcp", - "udp", - "ipc", - "unix-socket", - "named-pipe", - "xpc", -]); -export type TransportType = z.infer; - -/** Direction of a protocol frame. */ -export const frameDirectionSchema = z.enum(["sent", "received", "intercepted"]); - -/** A captured protocol frame. */ -export const protocolFrameSchema = z.strictObject({ - /** Monotonic sequence number. */ - sequence: z.number().int().nonnegative(), - /** Timestamp in milliseconds. */ - at_ms: z.number().int().nonnegative(), - /** Transport type. */ - transport: transportTypeSchema, - /** Direction of the frame. */ - direction: frameDirectionSchema, - /** Source process identity. */ - source_pid: z.number().int().nullable(), - /** Destination process identity. */ - dest_pid: z.number().int().nullable(), - /** Endpoint address (IP:port, socket path, pipe name). */ - source_endpoint: z.string().nullable(), - /** Destination endpoint. */ - dest_endpoint: z.string().nullable(), - /** Raw frame data as base64. */ - raw_data: z.string().nullable(), - /** Decoded/parsed frame content if known. */ - decoded_content: jsonValueSchema.nullable(), - /** Frame size in bytes. */ - size: z.number().int().nonnegative(), - /** Whether the frame was truncated. */ - truncated: z.boolean().default(false), -}); -export type ProtocolFrame = z.infer; - -/** Authentication flow stage. */ -export const authStageSchema = z.enum([ - "init", - "challenge", - "response", - "token_exchange", - "renewal", - "revocation", - "failure", - "success", -]); - -/** Token lifecycle event. */ -export const tokenLifecycleEventSchema = z.enum([ - "issued", - "refreshed", - "expired", - "revoked", - "renewed", -]); - -/** A captured authentication flow event. */ -export const authFlowEventSchema = z.strictObject({ - /** Monotonic sequence number. */ - sequence: z.number().int().nonnegative(), - /** Timestamp in milliseconds. */ - at_ms: z.number().int().nonnegative(), - /** Authentication stage. */ - stage: authStageSchema, - /** Protocol used. */ - protocol: z.string().min(1), - /** Token type if applicable. */ - token_type: z.string().nullable(), - /** Token lifecycle event. */ - token_lifecycle: tokenLifecycleEventSchema.nullable(), - /** Correlation ID linking challenge-response pairs. */ - correlation_id: z.string().nullable(), - /** Whether the capture producer marked credentials as redacted. */ - credentials_redacted: z.boolean().default(false), - /** Whether the authentication succeeded. */ - succeeded: z.boolean().default(false), - /** Error message if authentication failed. */ - error: z.string().nullable(), -}); -export type AuthFlowEvent = z.infer; - -/** A captured protocol session. */ -export const customProtocolCaptureSchema = z.strictObject({ - /** Transport type for this session. */ - transport: transportTypeSchema, - /** Captured frames in order. */ - frames: z.array(protocolFrameSchema), - /** Authentication flow events. */ - auth_events: z.array(authFlowEventSchema).default([]), - /** Whether any frame was truncated. */ - has_truncated: z.boolean().default(false), - /** Whether the capture producer detected credential content. */ - credentials_detected: z.boolean().default(false), -}); - -/** Correlate a frame with process identity. */ -export function correlateProcessIdentity( - frame: ProtocolFrame, - pid: number, -): boolean { - return frame.source_pid === pid || frame.dest_pid === pid; -} - -/** Check if a frame contains credential-like content. */ -export function looksLikeCredentialFrame(frame: ProtocolFrame): boolean { - const patterns = [ - "password", - "token", - "secret", - "api_key", - "apikey", - "authorization", - "credential", - ]; - const content = JSON.stringify(frame.decoded_content ?? "").toLowerCase(); - return patterns.some((p) => content.includes(p)); -} - -/** Filter frames by transport type. */ -export function framesByTransport( - frames: readonly ProtocolFrame[], - transport: TransportType, -): ProtocolFrame[] { - return frames.filter((f) => f.transport === transport); -} - -/** Get all authentication flow events for a specific protocol. */ -export function authEventsByProtocol( - events: readonly AuthFlowEvent[], - protocol: string, -): AuthFlowEvent[] { - return events.filter((e) => e.protocol === protocol); -} - -/** Get successful authentication events. */ -export function successfulAuthEvents( - events: readonly AuthFlowEvent[], -): AuthFlowEvent[] { - return events.filter((e) => e.succeeded); -} - -/** Get failed authentication events. */ -export function failedAuthEvents( - events: readonly AuthFlowEvent[], -): AuthFlowEvent[] { - return events.filter((e) => !e.succeeded); -} - -/** Compute the duration of an authentication flow in milliseconds. */ -export function authFlowDuration( - events: readonly AuthFlowEvent[], -): number | null { - const first = events.at(0); - const last = events.at(-1); - if (first === undefined || last === undefined) return null; - return last.at_ms - first.at_ms; -} diff --git a/src/domain/digests.test.ts b/src/domain/digests.test.ts index 9593cef6f..7beb70881 100644 --- a/src/domain/digests.test.ts +++ b/src/domain/digests.test.ts @@ -5,7 +5,6 @@ import { isDigest, isPrefixedDigest, prefixedDigestSchema, - SHA256_PATTERN, } from "./digests.js"; const DIGEST = "a".repeat(64); @@ -59,9 +58,4 @@ describe("digest shapes", () => { `jag_${OTHER_DIGEST}`, ); }); - - it("exposes the pattern for callers that cannot use zod", () => { - expect(SHA256_PATTERN.test(DIGEST)).toBe(true); - expect(SHA256_PATTERN.test(DIGEST.toUpperCase())).toBe(false); - }); }); diff --git a/src/domain/emptyArraySchema.test.ts b/src/domain/emptyArraySchema.test.ts deleted file mode 100644 index c2cf8a3da..000000000 --- a/src/domain/emptyArraySchema.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { emptyArraySchema } from "./emptyArraySchema.js"; - -describe("empty array schema", () => { - it("accepts only an empty array", () => { - expect(emptyArraySchema.parse([])).toEqual([]); - for (const value of [ - [null], - ["candidate"], - [0], - [false], - [{}], - [[]], - null, - {}, - ]) { - expect(emptyArraySchema.safeParse(value).success).toBe(false); - } - }); -}); diff --git a/src/domain/eventProcessTree.fixture.ts b/src/domain/eventProcessTree.fixture.ts deleted file mode 100644 index 77368a9ac..000000000 --- a/src/domain/eventProcessTree.fixture.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { ProcessNode, ProcessTreeEvent } from "./eventProcessTree.js"; - -/** - * Defaults shared by every fixture event. Tests vary only the fields they are - * actually asserting on, so a tree reads as the sequence of transitions under - * test rather than thirteen repeated literal fields per event. - */ -const eventDefaults: { - arguments: string[]; - previous_pid: number | null; - previous_ppid: number | null; - exit_code: number | null; - signal: number | null; - tid: number | null; -} = { - arguments: [], - previous_pid: null, - previous_ppid: null, - exit_code: null, - signal: null, - tid: null, -}; - -/** One `spawn` event, numbering itself from the sequence given. */ -export const spawnEvent = (event: { - sequence: number; - at_ms?: number; - pid: number; - ppid: number; - process_name: string; - executable?: string | null; - arguments?: string[]; -}): ProcessTreeEvent => ({ - ...eventDefaults, - at_ms: event.at_ms ?? event.sequence * 10, - type: "spawn", - executable: event.executable ?? `/${event.process_name}`, - ...event, -}); - -/** One `exit` event. */ -export const exitEvent = (event: { - sequence: number; - at_ms?: number; - pid: number; - ppid: number | null; - process_name: string | null; - exit_code?: number; -}): ProcessTreeEvent => ({ - ...eventDefaults, - at_ms: event.at_ms ?? event.sequence * 10, - type: "exit", - executable: null, - exit_code: 0, - ...event, -}); - -/** One `re_exec` event, which keeps the pid and replaces the executable. */ -export const reExecEvent = (event: { - sequence: number; - at_ms?: number; - pid: number; - ppid: number; - process_name: string; - executable: string; - arguments?: string[]; -}): ProcessTreeEvent => ({ - ...eventDefaults, - at_ms: event.at_ms ?? event.sequence * 10, - type: "re_exec", - previous_pid: null, - ...event, -}); - -/** One `reparent` event, which records the pid the process was adopted from. */ -export const reparentEvent = (event: { - sequence: number; - at_ms?: number; - pid: number; - ppid: number; - process_name: string; - previous_ppid: number; -}): ProcessTreeEvent => ({ - ...eventDefaults, - at_ms: event.at_ms ?? event.sequence * 10, - type: "reparent", - executable: null, - ...event, -}); - -/** A reconstructed node, defaulting to a childless process with no lifecycle. */ -export const processNode = (node: { - pid: number; - ppid: number | null; - process_name: string; - children?: number[]; -}): ProcessNode => ({ - executable: null, - arguments: [], - spawn_time_ms: null, - exit_time_ms: null, - exit_code: null, - is_re_exec: false, - is_reparented: false, - previous_ppid: null, - ...node, - children: node.children ?? [], -}); - -/** The common `spawn` of a root process at pid 1. */ -export const rootSpawn = (sequence = 0): ProcessTreeEvent => - spawnEvent({ sequence, pid: 1, ppid: 0, process_name: "root" }); diff --git a/src/domain/eventProcessTree.test.ts b/src/domain/eventProcessTree.test.ts deleted file mode 100644 index 179642180..000000000 --- a/src/domain/eventProcessTree.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - containsProcess, - getDescendants, - maxTreeDepth, - reconstructProcessTree, - type ProcessTreeEvent, -} from "./eventProcessTree.js"; -import { - exitEvent, - processNode, - reExecEvent, - reparentEvent, - rootSpawn, - spawnEvent, -} from "./eventProcessTree.fixture.js"; - -describe("event-backed process tree reconstruction", () => { - it("walks trees larger than ten thousand nodes", () => { - const childPids = Array.from({ length: 10_001 }, (_, index) => index + 2); - const root = processNode({ - pid: 1, - ppid: null, - process_name: "root", - children: childPids, - }); - const children = childPids.map((pid) => - processNode({ pid, ppid: 1, process_name: "root" }), - ); - - expect(maxTreeDepth([root, ...children], 1)).toBe(1); - expect(getDescendants([root, ...children], 1)).toHaveLength( - childPids.length, - ); - }); - - it("reconstructs a simple process tree", () => { - const events: ProcessTreeEvent[] = [ - rootSpawn(0), - spawnEvent({ - sequence: 1, - pid: 2, - ppid: 1, - process_name: "child", - executable: "/child", - }), - exitEvent({ - sequence: 2, - at_ms: 20, - pid: 2, - ppid: 1, - process_name: "child", - }), - exitEvent({ - sequence: 3, - at_ms: 30, - pid: 1, - ppid: 0, - process_name: "root", - }), - ]; - const result = reconstructProcessTree(events, 1); - expect(result.processes).toHaveLength(2); - expect(result.events_consumed).toBe(4); - expect(result.events_unmatched).toBe(0); - expect(result.short_lived_descendants).toBe(2); - }); -}); - -describe("short-lived process descendants", () => { - it("detects short-lived descendants", () => { - const events: ProcessTreeEvent[] = [ - rootSpawn(0), - spawnEvent({ - sequence: 1, - pid: 2, - ppid: 1, - process_name: "short", - executable: "/short", - }), - exitEvent({ - sequence: 2, - at_ms: 11, - pid: 2, - ppid: 1, - process_name: "short", - }), - ]; - const result = reconstructProcessTree(events, 1); - expect(result.short_lived_descendants).toBe(1); - }); -}); - -describe("event-backed process tree transitions", () => { - it("detects re-exec", () => { - const events: ProcessTreeEvent[] = [ - rootSpawn(0), - reExecEvent({ - sequence: 1, - pid: 1, - ppid: 0, - process_name: "root", - executable: "/newroot", - arguments: ["--new"], - }), - ]; - const result = reconstructProcessTree(events, 1); - expect(result.has_re_exec).toBe(true); - const root = result.processes.find((p) => p.pid === 1); - expect(root?.is_re_exec).toBe(true); - expect(root?.executable).toBe("/newroot"); - }); - - it("detects reparenting", () => { - const events: ProcessTreeEvent[] = [ - rootSpawn(0), - spawnEvent({ - sequence: 1, - pid: 2, - ppid: 1, - process_name: "child", - executable: "/child", - }), - reparentEvent({ - sequence: 2, - pid: 2, - ppid: 0, - process_name: "child", - previous_ppid: 1, - }), - ]; - const result = reconstructProcessTree(events, 1); - expect(result.has_reparenting).toBe(true); - const child = result.processes.find((p) => p.pid === 2); - expect(child?.is_reparented).toBe(true); - expect(child?.previous_ppid).toBe(1); - expect(child?.ppid).toBe(0); - }); - - it("reports unmatched events for unknown exits", () => { - const events: ProcessTreeEvent[] = [ - exitEvent({ - sequence: 0, - at_ms: 0, - pid: 999, - ppid: null, - process_name: null, - exit_code: 1, - }), - ]; - const result = reconstructProcessTree(events, 1); - expect(result.events_unmatched).toBe(1); - expect(result.processes).toHaveLength(0); - }); -}); - -describe("event-backed process tree queries", () => { - it("gets descendants", () => { - const events: ProcessTreeEvent[] = [ - rootSpawn(0), - spawnEvent({ - sequence: 1, - pid: 2, - ppid: 1, - process_name: "child1", - executable: "/c1", - }), - spawnEvent({ - sequence: 2, - pid: 3, - ppid: 2, - process_name: "grandchild", - executable: "/gc", - }), - ]; - const result = reconstructProcessTree(events, 1); - const descendants = getDescendants(result.processes, 1); - expect(descendants).toHaveLength(2); - expect(descendants).toContain(2); - expect(descendants).toContain(3); - }); - - it("checks process containment", () => { - const events: ProcessTreeEvent[] = [rootSpawn(0)]; - const result = reconstructProcessTree(events, 1); - expect(containsProcess(result.processes, 1)).toBe(true); - expect(containsProcess(result.processes, 999)).toBe(false); - }); -}); - -describe("event-backed process tree depth and types", () => { - it("computes max tree depth", () => { - const events: ProcessTreeEvent[] = [ - rootSpawn(0), - spawnEvent({ - sequence: 1, - pid: 2, - ppid: 1, - process_name: "child", - executable: "/c", - }), - spawnEvent({ - sequence: 2, - pid: 3, - ppid: 2, - process_name: "gc", - executable: "/gc", - }), - ]; - const result = reconstructProcessTree(events, 1); - expect(maxTreeDepth(result.processes, 1)).toBe(2); - }); -}); diff --git a/src/domain/eventProcessTree.ts b/src/domain/eventProcessTree.ts deleted file mode 100644 index ad04efd76..000000000 --- a/src/domain/eventProcessTree.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { z } from "zod"; - -/** Event types for event-backed process-tree capture. */ -export const processEventTypeSchema = z.enum([ - "spawn", - "exit", - "re_exec", - "reparent", - "signal", - "name_change", - "thread_create", - "thread_exit", -]); - -/** A single event-backed process-tree event. */ -export const processTreeEventSchema = z.strictObject({ - /** Monotonic event sequence number. */ - sequence: z.number().int().nonnegative(), - /** Event timestamp in milliseconds. */ - at_ms: z.number().int().nonnegative(), - /** Event type. */ - type: processEventTypeSchema, - /** Process ID. */ - pid: z.number().int().positive(), - /** Parent process ID. */ - ppid: z.number().int().nullable(), - /** Process name if known. */ - process_name: z.string().nullable(), - /** Executable path if known. */ - executable: z.string().nullable(), - /** Command line arguments. */ - arguments: z.array(z.string()).default([]), - /** Previous PID (for reparent events). */ - previous_pid: z.number().int().nullable(), - /** Previous PPID (for reparent events). */ - previous_ppid: z.number().int().nullable(), - /** Exit code (for exit events). */ - exit_code: z.number().int().nullable(), - /** Signal number (for signal events). */ - signal: z.number().int().nullable(), - /** Thread ID (for thread events). */ - tid: z.number().int().nullable(), -}); -export type ProcessTreeEvent = z.infer; - -/** Process node in a reconstructed tree. */ -export const processNodeSchema = z.strictObject({ - pid: z.number().int().positive(), - ppid: z.number().int().nullable(), - process_name: z.string().nullable(), - executable: z.string().nullable(), - arguments: z.array(z.string()).default([]), - children: z.array(z.number().int()).default([]), - spawn_time_ms: z.number().int().nullable(), - exit_time_ms: z.number().int().nullable(), - exit_code: z.number().int().nullable(), - is_re_exec: z.boolean().default(false), - is_reparented: z.boolean().default(false), - previous_ppid: z.number().int().nullable(), -}); -export type ProcessNode = z.infer; - -/** Result of event-backed process-tree reconstruction. */ -export const processTreeReconstructionSchema = z.strictObject({ - /** Root PID of the process tree. */ - root_pid: z.number().int().positive(), - /** All reconstructed process nodes. */ - processes: z.array(processNodeSchema).min(0), - /** Events that were consumed. */ - events_consumed: z.number().int().nonnegative(), - /** Events that could not be matched. */ - events_unmatched: z.number().int().nonnegative(), - /** Short-lived descendants that were missed by sampling. */ - short_lived_descendants: z.number().int().nonnegative(), - /** Whether any re-exec was detected. */ - has_re_exec: z.boolean().default(false), - /** Whether any reparenting was detected. */ - has_reparenting: z.boolean().default(false), -}); -export type ProcessTreeReconstruction = z.infer< - typeof processTreeReconstructionSchema ->; - -type ProcessTreeAccumulator = { - readonly nodes: Map; - readonly children: Map; - eventsUnmatched: number; - hasReExec: boolean; - hasReparenting: boolean; - shortLivedDescendants: number; -}; - -const applyProcessTreeEvent = ( - event: ProcessTreeEvent, - state: ProcessTreeAccumulator, -): void => { - let node = state.nodes.get(event.pid); - switch (event.type) { - case "spawn": { - if (!node) { - node = { - pid: event.pid, - ppid: event.ppid, - process_name: event.process_name, - executable: event.executable, - arguments: event.arguments, - children: [], - spawn_time_ms: event.at_ms, - exit_time_ms: null, - exit_code: null, - is_re_exec: false, - is_reparented: false, - previous_ppid: null, - }; - state.nodes.set(event.pid, node); - } - if (event.ppid !== null) { - const parentChildren = state.children.get(event.ppid) ?? []; - parentChildren.push(event.pid); - state.children.set(event.ppid, parentChildren); - } - break; - } - case "exit": - if (node) { - node.exit_time_ms = event.at_ms; - node.exit_code = event.exit_code; - if ( - node.spawn_time_ms !== null && - event.at_ms - node.spawn_time_ms < 100 - ) - state.shortLivedDescendants++; - } else state.eventsUnmatched++; - break; - case "re_exec": - if (node) { - node.is_re_exec = true; - node.executable = event.executable ?? node.executable; - node.arguments = event.arguments; - state.hasReExec = true; - } else state.eventsUnmatched++; - break; - case "reparent": - if (node) { - node.previous_ppid = node.ppid; - node.ppid = event.ppid; - node.is_reparented = true; - state.hasReparenting = true; - } else state.eventsUnmatched++; - break; - case "signal": - case "name_change": - case "thread_create": - case "thread_exit": - // These events are recorded but don't modify the tree structure. - break; - } -}; - -/** Reconstruct a process tree from event-backed capture. */ -export function reconstructProcessTree( - events: readonly ProcessTreeEvent[], - rootPid: number, -): ProcessTreeReconstruction { - const nodes = new Map(); - const children = new Map(); - let eventsConsumed = 0; - const state: ProcessTreeAccumulator = { - nodes, - children, - eventsUnmatched: 0, - hasReExec: false, - hasReparenting: false, - shortLivedDescendants: 0, - }; - - const sorted = [...events].sort((a, b) => a.at_ms - b.at_ms); - - for (const event of sorted) { - eventsConsumed++; - applyProcessTreeEvent(event, state); - } - - // Attach children to nodes - for (const [pid, childPids] of children) { - const node = nodes.get(pid); - if (node) node.children = childPids; - } - - return { - root_pid: rootPid, - processes: [...nodes.values()], - events_consumed: eventsConsumed, - events_unmatched: state.eventsUnmatched, - short_lived_descendants: state.shortLivedDescendants, - has_re_exec: state.hasReExec, - has_reparenting: state.hasReparenting, - }; -} - -/** Get all descendant PIDs of a given root PID. */ -export function getDescendants( - processes: readonly ProcessNode[], - rootPid: number, -): number[] { - const byPid = new Map(processes.map((p) => [p.pid, p])); - const visited = new Set(); - const result: number[] = []; - const queue: number[] = [rootPid]; - for (let head = 0; head < queue.length; head += 1) { - const pid = queue[head]; - if (pid === undefined) continue; - if (visited.has(pid)) continue; - visited.add(pid); - const node = byPid.get(pid); - if (node) { - for (const child of node.children) { - if (!visited.has(child)) { - result.push(child); - queue.push(child); - } - } - } - } - return result; -} - -/** Check if a process tree contains a specific PID. */ -export function containsProcess( - processes: readonly ProcessNode[], - pid: number, -): boolean { - return processes.some((p) => p.pid === pid); -} - -/** Get the maximum tree depth from the root. */ -export function maxTreeDepth( - processes: readonly ProcessNode[], - rootPid: number, -): number { - const byPid = new Map(processes.map((p) => [p.pid, p])); - const pending = [{ pid: rootPid, depth: 0 }]; - const visited = new Set(); - let maximumDepth = 0; - - while (pending.length > 0) { - const current = pending.pop(); - if (!current || visited.has(current.pid)) continue; - visited.add(current.pid); - - const node = byPid.get(current.pid); - if (!node || node.children.length === 0) continue; - - const childDepth = current.depth + 1; - maximumDepth = Math.max(maximumDepth, childDepth); - for (const childPid of node.children) - if (!visited.has(childPid)) - pending.push({ pid: childPid, depth: childDepth }); - } - - return maximumDepth; -} diff --git a/src/domain/evidence.test.ts b/src/domain/evidence.test.ts index c13663709..29a629d3d 100644 --- a/src/domain/evidence.test.ts +++ b/src/domain/evidence.test.ts @@ -156,6 +156,23 @@ describe("analysis evidence identity", () => { }); }); +it("binds an explicit unknown-subject reason into the semantic Evidence identity", () => { + const reason = "Caller excluded the observed digest."; + const observation = { operation: "health", parameters: {}, result: true }; + const explicit = createEvidence(undefined, PROVIDER, { + ...observation, + subjectUnavailableReason: reason, + }); + const defaulted = createEvidence(undefined, PROVIDER, observation); + expect(explicit.subject).toBeNull(); + expect(explicit.limitations).toEqual([reason]); + expect(defaulted.limitations).toEqual([ + "Artifact identity is unavailable for this observation.", + ]); + expect(explicit.evidence_id).not.toBe(defaulted.evidence_id); + expect(parseEvidence(explicit)).toEqual(explicit); +}); + it("derives byte-stable bundle manifests independent of record order", () => { const artifactEvidence = createEvidence(TARGET, PROVIDER, { operation: "health", diff --git a/src/domain/evidence.ts b/src/domain/evidence.ts index 014acb7ed..6348fc38a 100644 --- a/src/domain/evidence.ts +++ b/src/domain/evidence.ts @@ -169,6 +169,8 @@ export interface EvidenceObservation { readonly authority?: EvidenceAuthority; readonly environment?: ExecutionEnvironment | null; readonly limitations?: readonly string[]; + /** Reason the subject identity is unavailable, when no target identity is supplied. */ + readonly subjectUnavailableReason?: string; readonly locations?: readonly EvidenceLocation[]; readonly evidenceLinks?: readonly string[]; } @@ -229,7 +231,7 @@ export const createEvidence = ( target === undefined ? null : { - name: target.path.split("/").at(-1) ?? target.path, + name: target.path.split("/").at(-1) || "artifact", digest: { sha256: target.sha256 }, format: target.format, architecture: target.architecture ?? null, @@ -262,7 +264,10 @@ export const createEvidence = ( environment: observation.environment ?? null, limitations: [ ...(target === undefined - ? ["Artifact identity is unavailable for this observation."] + ? [ + observation.subjectUnavailableReason ?? + "Artifact identity is unavailable for this observation.", + ] : []), ...(observation.limitations ?? []), ], diff --git a/src/domain/evidenceBundle.test.ts b/src/domain/evidenceBundle.test.ts new file mode 100644 index 000000000..00e5f7b4f --- /dev/null +++ b/src/domain/evidenceBundle.test.ts @@ -0,0 +1,120 @@ +import { describe, expect, it } from "vitest"; + +import { createEvidence } from "./evidence.js"; +import { + createEvidenceBundle, + evidenceBundleForTarget, + parseEvidenceBundle, +} from "./evidenceBundle.js"; +import { + createResidualUnknown, + recordUnknownInputSchema, +} from "./residualUnknown.js"; + +const provider = { id: "fixture", name: "Fixture", version: "1" }; +const targetDigest = "a".repeat(64); +const foreignDigest = "b".repeat(64); + +const makeUnknown = ( + question: string, + scopeDigest: string, + dependency?: string, +) => { + const input = recordUnknownInputSchema.parse({ + question, + severity: "medium", + domain: "bundle-projection", + required_authority: null, + required_confidence: "derived", + required_environment: null, + recommended_probes: [], + relationships: + dependency === undefined + ? [] + : [{ type: "depends-on", unknown_id: dependency }], + }); + const mutation = createEvidence(undefined, provider, { + predicateType: "rea.residual-unknown-mutation", + operation: "record_unknown", + parameters: { domain: input.domain, severity: input.severity }, + result: { + action: "record", + question: input.question, + required_authority: input.required_authority, + required_confidence: input.required_confidence, + }, + }); + return { + evidence: mutation, + unknown: createResidualUnknown(input, mutation.evidence_id, scopeDigest), + }; +}; + +describe("evidenceBundleForTarget", () => { + it("removes dependent unknowns when their foreign-scope root is excluded", () => { + const foreignRoot = makeUnknown("Foreign root", foreignDigest); + const child = makeUnknown( + "Target child", + targetDigest, + foreignRoot.unknown.unknown_id, + ); + const grandchild = makeUnknown( + "Target grandchild", + targetDigest, + child.unknown.unknown_id, + ); + const retained = makeUnknown("Independent target", targetDigest); + const bundle = createEvidenceBundle( + [ + foreignRoot.evidence, + child.evidence, + grandchild.evidence, + retained.evidence, + ], + [ + foreignRoot.unknown, + child.unknown, + grandchild.unknown, + retained.unknown, + ], + ); + + expect(evidenceBundleForTarget(bundle, targetDigest)).toEqual( + createEvidenceBundle([retained.evidence], [retained.unknown]), + ); + }); + + it("projects a valid graph with more than 125,000 dependents", () => { + const foreignRoot = makeUnknown("Foreign high-fan-out root", foreignDigest); + const middle = makeUnknown( + "Target middle depending on foreign root", + targetDigest, + foreignRoot.unknown.unknown_id, + ); + const unknowns = [foreignRoot.unknown, middle.unknown]; + for (let index = 0; index < 125_000; index += 1) { + const input = recordUnknownInputSchema.parse({ + question: `Target child ${index}`, + severity: "medium", + domain: "bundle-projection", + required_authority: null, + required_confidence: "derived", + required_environment: null, + recommended_probes: [], + relationships: [ + { type: "depends-on", unknown_id: middle.unknown.unknown_id }, + ], + }); + unknowns.push( + createResidualUnknown(input, middle.evidence.evidence_id, targetDigest), + ); + } + const bundle = parseEvidenceBundle( + createEvidenceBundle([foreignRoot.evidence, middle.evidence], unknowns), + ); + + expect(evidenceBundleForTarget(bundle, targetDigest)).toEqual( + createEvidenceBundle([], []), + ); + }); +}); diff --git a/src/domain/evidenceBundle.ts b/src/domain/evidenceBundle.ts index 665ccad7b..1f92146ea 100644 --- a/src/domain/evidenceBundle.ts +++ b/src/domain/evidenceBundle.ts @@ -111,28 +111,57 @@ export const evidenceBundleForTarget = ( const recordsById = new Map( bundle.records.map((record) => [record.evidence_id, record]), ); - let unknowns = bundle.unknowns.filter( + const candidates = bundle.unknowns.filter( ({ scope_digest: scopeDigest }) => scopeDigest === sha256, ); - let changed = true; - while (changed) { - const ids = new Set(unknowns.map(({ unknown_id: id }) => id)); - const retained = unknowns.filter( - (unknown) => - unknown.relationships.every(({ unknown_id: id }) => ids.has(id)) && - referencedEvidenceIds(unknown).every((id) => { - const record = recordsById.get(id); - return ( - record?.subject?.digest.sha256 === sha256 || - (unknown.mutation_evidence_ids.includes(id) && - record?.subject === null && - record.predicate_type === "rea.residual-unknown-mutation") - ); - }), + const retained = new Set(); + const revisionsById = new Map(); + const dependents = new Map>(); + for (const unknown of candidates) { + const mutationEvidenceIds = new Set(unknown.mutation_evidence_ids); + const evidenceAvailable = referencedEvidenceIds(unknown).every((id) => { + const record = recordsById.get(id); + return ( + record?.subject?.digest.sha256 === sha256 || + (mutationEvidenceIds.has(id) && + record?.subject === null && + record.predicate_type === "rea.residual-unknown-mutation") + ); + }); + if (!evidenceAvailable) continue; + retained.add(unknown); + revisionsById.set( + unknown.unknown_id, + (revisionsById.get(unknown.unknown_id) ?? 0) + 1, ); - changed = retained.length !== unknowns.length; - unknowns = retained; + for (const relatedId of new Set( + unknown.relationships.map(({ unknown_id: id }) => id), + )) { + const references = + dependents.get(relatedId) ?? new Set(); + references.add(unknown); + dependents.set(relatedId, references); + } } + const pending: ResidualUnknown[] = []; + for (const unknown of retained) + if ( + unknown.relationships.some(({ unknown_id: id }) => !revisionsById.has(id)) + ) + pending.push(unknown); + for (let index = 0; index < pending.length; index += 1) { + const unknown = pending[index]; + if (unknown === undefined || !retained.delete(unknown)) continue; + const remaining = (revisionsById.get(unknown.unknown_id) ?? 1) - 1; + if (remaining > 0) { + revisionsById.set(unknown.unknown_id, remaining); + continue; + } + revisionsById.delete(unknown.unknown_id); + for (const dependent of dependents.get(unknown.unknown_id) ?? []) + pending.push(dependent); + } + const unknowns = candidates.filter((unknown) => retained.has(unknown)); const mutationIds = new Set( unknowns.flatMap(({ mutation_evidence_ids: ids }) => ids), ); @@ -285,26 +314,28 @@ export const validateResidualUnknownAddition = ( ); if (unknown.resolution?.disposition === "verified") validateVerifiedResolution(unknown, evidenceById); - for (const relationship of unknown.relationships) + // In a valid graph, no existing node can point to an absent ID. Since the + // schema rejects self-relations, a new node's outgoing edges cannot close a + // cycle; keep graph traversal for revisions of existing nodes only. + if (current !== undefined) { + const dependencies = unknown.relationships + .filter((relationship) => relationship.type === "depends-on") + .map(({ unknown_id }) => unknown_id); if ( - relationship.type === "depends-on" && - reachesUnknown( - relationship.unknown_id, - unknown.unknown_id, - currentHeads, - unknown, - ) + dependencies.length > 0 && + reachesUnknown(dependencies, unknown.unknown_id, currentHeads, unknown) ) throw new TypeError("Residual unknown dependency graph contains a cycle"); + } }; const reachesUnknown = ( - start: string, + starts: readonly string[], goal: string, currentHeads: ReadonlyMap, candidate: ResidualUnknown, ): boolean => { - const pending = [start]; + const pending = [...starts]; const visited = new Set(); while (pending.length > 0) { const id = pending.pop(); @@ -356,20 +387,56 @@ const rejectDependencyCycles = ( ): void => { const visited = new Set(); const active = new Set(); - const visit = (id: string): void => { - if (active.has(id)) - throw new TypeError("Residual unknown dependency graph contains a cycle"); - if (visited.has(id)) return; - active.add(id); - const unknown = unknownById.get(id); - if (unknown === undefined) + const pending: { + readonly id: string; + readonly relationships: ResidualUnknown["relationships"]; + nextRelationship: number; + }[] = []; + for (const root of unknownById.keys()) { + if (visited.has(root)) continue; + const rootUnknown = unknownById.get(root); + if (rootUnknown === undefined) throw new TypeError("Residual unknown dependency graph is inconsistent"); - for (const relationship of unknown.relationships) - if (relationship.type === "depends-on") visit(relationship.unknown_id); - active.delete(id); - visited.add(id); - }; - for (const id of unknownById.keys()) visit(id); + active.add(root); + pending.push({ + id: root, + relationships: rootUnknown.relationships, + nextRelationship: 0, + }); + while (pending.length > 0) { + const frame = pending[pending.length - 1]; + if (frame === undefined) break; + let descended = false; + while (frame.nextRelationship < frame.relationships.length) { + const relationship = frame.relationships[frame.nextRelationship]; + frame.nextRelationship += 1; + if (relationship?.type !== "depends-on") continue; + const dependency = relationship.unknown_id; + if (active.has(dependency)) + throw new TypeError( + "Residual unknown dependency graph contains a cycle", + ); + if (visited.has(dependency)) continue; + const unknown = unknownById.get(dependency); + if (unknown === undefined) + throw new TypeError( + "Residual unknown dependency graph is inconsistent", + ); + active.add(dependency); + pending.push({ + id: dependency, + relationships: unknown.relationships, + nextRelationship: 0, + }); + descended = true; + break; + } + if (descended) continue; + pending.pop(); + active.delete(frame.id); + visited.add(frame.id); + } + } }; /** Encode a validated bundle as byte-stable RFC 8785 canonical JSON. */ diff --git a/src/domain/explicitSensitiveFailure.test.ts b/src/domain/explicitSensitiveFailure.test.ts new file mode 100644 index 000000000..28d935b82 --- /dev/null +++ b/src/domain/explicitSensitiveFailure.test.ts @@ -0,0 +1,126 @@ +import { AnalysisAccessDeniedError } from "./analysisErrorCore.js"; +import { expect, it } from "vitest"; +import { + AnalysisInputError, + AnalysisCapabilityUnavailableError, +} from "./analysisErrorCore.js"; +import { projectAnalysisError } from "./analysisErrorProjection.js"; +import { ProviderCleanupError } from "./providerCleanupError.js"; +import { + redactExplicitFailure, + redactExplicitPointer, +} from "./explicitSensitiveFailure.js"; + +it.each([ + ["/metadata/private~1~0/numeric", ["private/~"], "/metadata"], + ["/metadata/private/child", ["private"], "/metadata"], + ["/ordinary/path", [], "/ordinary/path"], +])( + "preserves a real parent for excluded pointer identities: %s", + (pointer, values, expected) => { + expect(redactExplicitPointer(pointer, values)).toBe(expected); + }, +); + +it("preserves cleanup failure authority and resources while excluding diagnostics", () => { + const error = new ProviderCleanupError( + "har", + ["/root/private/capture", "pid:123"], + { + capture_path: "/root/private/capture", + nested: { "private-key": 5, ordinary: "private ordinary" }, + exit_code: 2, + }, + { operation: "inspect_web_network_capture" }, + ); + const projected = projectAnalysisError( + redactExplicitFailure(error, ["private", "REDACTED"]), + ); + expect(projected.code).toBe("cleanup_incomplete"); + expect(JSON.stringify(projected)).not.toContain("private"); + expect(JSON.stringify(projected)).not.toContain("REDACTED"); + expect(JSON.stringify(projected)).toContain("pid:123"); + expect(JSON.stringify(projected)).toContain('"exit_code":2'); +}); + +it("keeps unsupported profile reasons distinct from malformed input", () => { + const error = new AnalysisCapabilityUnavailableError( + "mitmproxy", + "inspect_web_network_capture", + "Unsupported private profile", + { userMessage: "Select private-compatible profile" }, + ); + const result = redactExplicitFailure(error, ["private"]); + expect(result).toBeInstanceOf(AnalysisCapabilityUnavailableError); + expect(projectAnalysisError(result).code).toBe("capability_unavailable"); + expect(JSON.stringify(projectAnalysisError(result))).not.toContain("private"); +}); + +it("coarsens format issue pointers without creating replacement coordinates", () => { + const error = new AnalysisInputError( + "inspect_web_network_capture", + undefined, + [ + { + path: ["capture_path", "/metadata/private~1~0/nested"], + reason: "invalid_format", + message: "Malformed private field", + }, + ], + ); + const result = redactExplicitFailure(error, ["private/~", "private"]); + expect(result).toBeInstanceOf(AnalysisInputError); + if (!(result instanceof AnalysisInputError)) + throw new Error("Input error required"); + expect(result.issues[0]?.path).toEqual(["capture_path", "/metadata"]); +}); + +it("excludes a marked permission-denied path without losing its host error code", () => { + const error = redactExplicitFailure( + new AnalysisAccessDeniedError( + "inspect_web_network_capture", + "/declared-path-value/capture.har", + "EACCES", + ), + ["declared-path-value"], + ); + expect(error._tag).toBe("AnalysisAccessDeniedError"); + const projected = projectAnalysisError(error); + expect(projected).toMatchObject({ + code: "access_denied", + category: "unavailable", + details: { system_code: "EACCES" }, + }); + expect(JSON.stringify(projected)).not.toContain("declared-path-value"); +}); + +it("coarsens ordinary sensitive issue-path segments to their real parent", () => { + const input = new AnalysisInputError( + "inspect_web_network_capture", + undefined, + [ + { + path: ["input", "mysecret", "child"], + reason: "unknown_argument", + message: "Unrecognized mysecret", + }, + ], + ); + const result = redactExplicitFailure(input, ["secret"]); + if (!(result instanceof AnalysisInputError)) + throw new Error("Input error required"); + expect(result.issues[0]?.path).toEqual(["input"]); + expect(JSON.stringify(projectAnalysisError(result))).not.toContain("secret"); +}); + +it("does not reinterpret an unknown argument name as a JSON pointer", () => { + const input = new AnalysisInputError( + "inspect_web_network_capture", + undefined, + [{ path: ["/ordinary/secret"], reason: "unknown_argument" }], + ); + const result = redactExplicitFailure(input, ["secret"]); + if (!(result instanceof AnalysisInputError)) + throw new Error("Input error required"); + expect(result.issues[0]?.path).toEqual([]); +}); diff --git a/src/domain/explicitSensitiveFailure.ts b/src/domain/explicitSensitiveFailure.ts new file mode 100644 index 000000000..f26324f9b --- /dev/null +++ b/src/domain/explicitSensitiveFailure.ts @@ -0,0 +1,136 @@ +import type { AnalysisError } from "./analysisErrorBase.js"; +import { + AnalysisAccessDeniedError, + AnalysisArtifactChangedError, + AnalysisInputError, + AnalysisOutputError, + AnalysisCapabilityUnavailableError, +} from "./analysisErrorCore.js"; +import { redactExplicitText } from "./explicitSensitiveValues.js"; +import type { JsonValue } from "./jsonValue.js"; +import { ProviderAdapterError } from "./providerAdapterError.js"; +import { ProviderCleanupError } from "./providerCleanupError.js"; + +/** Keep a real JSON-pointer ancestor when a property identity is explicitly excluded. */ +export const redactExplicitPointer = ( + pointer: string, + values: readonly string[], +): string => { + let parent = ""; + for (const segment of pointer.split("/").slice(1)) { + const decoded = segment.replaceAll("~1", "/").replaceAll("~0", "~"); + const child = `${parent}/${segment}`; + if ( + values.some( + (literal) => decoded.includes(literal) || child.includes(literal), + ) + ) + return parent; + parent = child; + } + return parent; +}; + +/** Exclude explicit literals from common acquisition/decoder failures, preserving typed diagnostics. */ +export const redactExplicitFailure = ( + error: AnalysisError, + values: readonly string[], +): AnalysisError => { + if (values.length === 0) return error; + const text = (value: string): string => redactExplicitText(value, values); + const path = ( + parts: readonly (string | number)[], + ordinaryNames: boolean, + ): (string | number)[] => { + const retained: (string | number)[] = []; + for (const part of parts) { + if (typeof part !== "string") { + retained.push(part); + } else if (!ordinaryNames && part.startsWith("/")) { + const pointer = redactExplicitPointer(part, values); + retained.push(pointer); + if (pointer !== part) break; + } else { + if (values.some((literal) => part.includes(literal))) break; + retained.push(part); + } + } + return retained; + }; + const diagnostics = ( + value: Readonly>, + ): Record => + Object.fromEntries( + Object.entries(value) + .filter(([key]) => !values.some((literal) => key.includes(literal))) + .map(([key, item]) => [key, visit(item)]), + ); + const visit = (value: JsonValue): JsonValue => { + if (typeof value === "string") return text(value); + if (Array.isArray(value)) return value.map(visit); + if (typeof value === "object" && value !== null) return diagnostics(value); + return value; + }; + if (error instanceof AnalysisAccessDeniedError) + return new AnalysisAccessDeniedError( + error.operation, + text(error.path), + error.systemCode, + { cause: error }, + ); + if (error instanceof AnalysisArtifactChangedError) + return new AnalysisArtifactChangedError( + error.operation, + text(error.path), + text(error.reason), + { cause: error }, + ); + if (error instanceof AnalysisInputError) + return new AnalysisInputError( + error.operation, + { cause: error }, + error.issues.map((issue) => ({ + ...issue, + // Unknown-argument paths contain literal field names, including names + // beginning with '/'; they are not producer JSON-pointer coordinates. + path: path(issue.path, issue.reason === "unknown_argument"), + ...(issue.message === undefined + ? {} + : { message: text(issue.message) }), + ...(issue.expected === undefined + ? {} + : { expected: visit(issue.expected) }), + })), + ); + if (error instanceof AnalysisOutputError) + return new AnalysisOutputError(error.operation, text(error.reason), { + cause: error, + }); + if (error instanceof AnalysisCapabilityUnavailableError) + return new AnalysisCapabilityUnavailableError( + error.providerId, + error.operation, + text(error.reason), + { + cause: error, + ...(error.userMessage === undefined + ? {} + : { userMessage: text(error.userMessage) }), + }, + ); + if (error instanceof ProviderCleanupError) + return new ProviderCleanupError( + error.providerId, + error.cleanupResources.map(text), + diagnostics(error.diagnostics ?? {}), + { operation: error.operation, cause: error }, + ); + if (error instanceof ProviderAdapterError) + return new ProviderAdapterError(error.providerId, error.operation, { + cause: error, + ...(error.diagnostics === undefined + ? {} + : { diagnostics: diagnostics(error.diagnostics) }), + }); + return error; +}; diff --git a/src/domain/explicitSensitiveValues.test.ts b/src/domain/explicitSensitiveValues.test.ts new file mode 100644 index 000000000..64c03a9d7 --- /dev/null +++ b/src/domain/explicitSensitiveValues.test.ts @@ -0,0 +1,22 @@ +import { expect, it } from "vitest"; +import { redactExplicitText } from "./explicitSensitiveValues.js"; + +it.each([ + ["secret context", ["secret"]], + ["secret context", ["secret", "REDACTED"]], + ["secret context", ["secret", "[", "…"]], + ["secretable context", ["secret", "able", "table"]], + ["secretsecret context", ["secret", "…", "REDACTED"]], +] as const)( + "keeps declared literals out of replacement text: %s", + (input, values) => { + const result = redactExplicitText(input, values); + for (const value of values) expect(result).not.toContain(value); + }, +); + +it("does not guess sensitivity or treat empty declarations as matches", () => { + expect(redactExplicitText("API_SECRET ordinary evidence", [""])).toBe( + "API_SECRET ordinary evidence", + ); +}); diff --git a/src/domain/explicitSensitiveValues.ts b/src/domain/explicitSensitiveValues.ts new file mode 100644 index 000000000..52c115c81 --- /dev/null +++ b/src/domain/explicitSensitiveValues.ts @@ -0,0 +1,20 @@ +/** Replace caller-declared literal text values without guessing sensitivity from names. */ +export const redactExplicitText = ( + value: string, + sensitiveValues: readonly string[], +): string => { + const literals = [...new Set(sensitiveValues)].filter( + (literal) => literal !== "", + ); + const replacement = + ["[REDACTED]", "…", ""].find((candidate) => + literals.every((literal) => !candidate.includes(literal)), + ) ?? ""; + let result = value; + for (const literal of literals.sort( + (left, right) => right.length - left.length, + )) + result = result.replaceAll(literal, replacement); + // Replacement boundaries can form another declaration; exclude that text. + return literals.some((literal) => result.includes(literal)) ? "" : result; +}; diff --git a/src/domain/firmware/firmwareAnalysis.ts b/src/domain/firmware/firmwareAnalysis.ts index 31bb12c3f..8a7f1f5de 100644 --- a/src/domain/firmware/firmwareAnalysis.ts +++ b/src/domain/firmware/firmwareAnalysis.ts @@ -1,11 +1,21 @@ import { z } from "zod"; import { digestSchema } from "../digests.js"; import { jsonValueSchema } from "../jsonValue.js"; +import { isAbsoluteLocalPath } from "../localPath.js"; const bytes = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER); const range = z.strictObject({ offset: bytes, length: bytes.positive() }); const target = { - path: z.string().min(1).describe("Local firmware file; never executed"), + path: z + .string() + .min(1) + .refine(isAbsoluteLocalPath, { + message: + "path must be an absolute local filesystem path (for example /tmp/firmware.bin or C:\\firmware\\firmware.bin)", + }) + .describe( + "Absolute local firmware file; never executed. Relative paths are rejected.", + ), }; /** Explicit firmware inspection and extraction intent, independent of engines. */ diff --git a/src/domain/functionComparison.test.ts b/src/domain/functionComparison.test.ts index 706dc7759..4adf6becd 100644 --- a/src/domain/functionComparison.test.ts +++ b/src/domain/functionComparison.test.ts @@ -246,6 +246,112 @@ describe("function collection collation ties", () => { }); }); +describe("function comparison ordering controls", () => { + const composed = { address: "0x2000", name: "caf\u00e9" }; + const decomposed = { address: "0x3000", name: "cafe\u0301" }; + + it("preserves duplicate Unicode collections and nullable endpoints", () => { + const controlled = (base: "0x1000" | "0x2000", reverse: boolean) => { + const endpoint = base === "0x1000" ? "0x1010" : "0x2010"; + const edges = [ + { + source_address: base, + target_address: endpoint, + source_procedure: null, + target_procedure: { address: endpoint, name: "caf\u00e9" }, + kind: { + available: true, + provenance: "provider-reference-manager", + type: "UNCONDITIONAL_CALL", + flow: true, + call: true, + jump: false, + data: false, + read: false, + write: false, + indirect: false, + computed: false, + conditional: false, + terminal: false, + primary: true, + operand_index: 0, + external: false, + }, + }, + { + source_address: base, + target_address: endpoint, + source_procedure: { address: base, name: "dispatch" }, + target_procedure: null, + kind: { + available: true, + provenance: "provider-reference-manager", + type: "READ", + flow: false, + call: false, + jump: false, + data: true, + read: true, + write: false, + indirect: false, + computed: false, + conditional: false, + terminal: false, + primary: true, + operand_index: 1, + external: false, + }, + }, + ]; + const strings = [ + { address: endpoint, source_address: base, value: "caf\u00e9" }, + { address: endpoint, source_address: base, value: "cafe\u0301" }, + { address: endpoint, source_address: base, value: "cafe\u0301" }, + ]; + return functionDossierSchema.parse({ + ...dossier("return 0;", base), + callees: [composed, decomposed, composed], + incoming_references: reverse ? [...edges].reverse() : edges, + outgoing_references: reverse ? [...edges].reverse() : edges, + referenced_strings: reverse ? [...strings].reverse() : strings, + referenced_names: reverse ? [...strings].reverse() : strings, + }); + }; + const makeEvidence = (digit: string, result: unknown): Evidence => + createEvidence( + { + path: `/tmp/function-${digit}`, + sha256: digit.repeat(64), + format: "mach-o", + }, + { id: "rea-workflow", name: "REA workflow", version: "1" }, + { + operation: "analyze_function", + parameters: enhancedInputSchemas.analyze_function.parse({ + procedure: "dispatch", + }), + result: jsonValueSchema.parse(result), + confidence: "derived", + authority: "shipped-artifact", + }, + ); + const left = controlled("0x1000", false); + const right = controlled("0x2000", true); + const { native_api: _nativeApi, ...rightWithoutNativeApi } = right; + + const comparison = compareFunctions( + makeEvidence("d", left), + makeEvidence("e", rightWithoutNativeApi), + ); + + expect(comparison.status).toBe("unchanged"); + for (const dimension of ["calls", "references", "strings_names"] as const) + expect( + comparison.dimensions.find((item) => item.dimension === dimension), + ).toMatchObject({ status: "unchanged" }); + }); +}); + describe("function comparison CFG address normalization", () => { it("matches CFG successors by numeric address", () => { const make = (base: "0x1000" | "0x2000") => diff --git a/src/domain/functionComparisonNormalization.ts b/src/domain/functionComparisonNormalization.ts index 43871b123..804127675 100644 --- a/src/domain/functionComparisonNormalization.ts +++ b/src/domain/functionComparisonNormalization.ts @@ -177,15 +177,19 @@ export const stringAndNameProjection = ( }; export const sorted = (values: readonly unknown[]): readonly unknown[] => - [...values].sort((left, right) => { - const leftJson = canonicalJson(left, "Function normalization"); - const rightJson = canonicalJson(right, "Function normalization"); - // Preserve existing collation while ordering distinct values within a tie. - return ( - leftJson.localeCompare(rightJson) || - compareCodePoints(leftJson, rightJson) - ); - }); + values + .map((value) => ({ + value, + json: canonicalJson(value, "Function normalization"), + })) + .sort((left, right) => { + // Preserve existing collation while ordering distinct values within a tie. + return ( + left.json.localeCompare(right.json) || + compareCodePoints(left.json, right.json) + ); + }) + .map(({ value }) => value); export const combineCoverage = ( left: FunctionCollection, diff --git a/src/domain/inputIssueProjection.test.ts b/src/domain/inputIssueProjection.test.ts index 0abc83431..45a241d9e 100644 --- a/src/domain/inputIssueProjection.test.ts +++ b/src/domain/inputIssueProjection.test.ts @@ -2,8 +2,45 @@ import { describe, expect, it } from "vitest"; import { z } from "zod"; import { projectInputIssues } from "./inputIssueProjection.js"; +import { processScenarioSchema } from "./process/processScenario.js"; describe("input issue projection", () => { + it("preserves static regex guidance without echoing the rejected value", () => { + const schema = z.object({ + value: z.string().regex(/^[^\0]*$/u, "Values cannot contain NUL"), + }); + const input = { value: "private\0value" }; + const parsed = schema.safeParse(input); + if (parsed.success) throw new Error("expected invalid input"); + + expect(projectInputIssues(parsed.error.issues, input)).toEqual([ + { + path: ["value"], + reason: "invalid_format", + expected: "regex", + message: "Values cannot contain NUL", + }, + ]); + }); + + it("projects reserved process environment key constraints at the key path", () => { + const input = { + executable: "/usr/bin/true", + environment: { REA_PROCESS_RUN_ID: "caller-value" }, + }; + const parsed = processScenarioSchema.safeParse(input); + if (parsed.success) throw new Error("expected invalid input"); + + expect(projectInputIssues(parsed.error.issues, input)).toEqual([ + { + path: ["environment", "REA_PROCESS_RUN_ID"], + reason: "invalid_format", + expected: "regex", + message: "REA_PROCESS_RUN_ID is reserved by the process adapter", + }, + ]); + }); + it("preserves schema-authored custom correction guidance", () => { const schema = z .object({ left: z.string().optional(), right: z.string().optional() }) diff --git a/src/domain/inputIssueProjection.ts b/src/domain/inputIssueProjection.ts index 08390df5a..31d158743 100644 --- a/src/domain/inputIssueProjection.ts +++ b/src/domain/inputIssueProjection.ts @@ -27,6 +27,13 @@ const projectIssue = ( ), ), ); + if (issue.code === "invalid_key") + return issue.issues.flatMap((keyIssue) => + projectIssue( + { ...keyIssue, path: [...issue.path, ...keyIssue.path] }, + input, + ), + ); if (issue.code === "unrecognized_keys") return issue.keys.map((key) => ({ path: [...path, key], @@ -48,7 +55,16 @@ const projectIssue = ( if (issue.code === "too_big") return [boundedIssue(path, "maximum", numericBound(issue.maximum))]; if (issue.code === "invalid_format") - return [{ path, reason: "invalid_format", expected: issue.format }]; + return [ + { + path, + reason: "invalid_format", + expected: issue.format, + ...(issue.format === "regex" && issue.message.length > 0 + ? { message: issue.message } + : {}), + }, + ]; if (issue.code === "invalid_value") return [ { diff --git a/src/domain/javascript/electronActiveObservation.ts b/src/domain/javascript/electronActiveObservation.ts index 02d60b748..6c1391266 100644 --- a/src/domain/javascript/electronActiveObservation.ts +++ b/src/domain/javascript/electronActiveObservation.ts @@ -1,6 +1,11 @@ import { z } from "zod"; -const pathInputSchema = z.string().trim().min(1); +import { isAbsoluteLocalPath } from "../localPath.js"; + +const pathInputSchema = z.string().trim().min(1).refine(isAbsoluteLocalPath, { + message: + "Electron executable, application, and root paths must be absolute local filesystem paths (for example /Applications/Electron.app/Contents/MacOS/Electron)", +}); const absolutePathSchema = z .string() .min(1) @@ -60,9 +65,17 @@ const actionSchema = z.discriminatedUnion("kind", [ /** Input for one explicit, provider-owned Electron runtime experiment. */ export const electronActiveObservationInputSchema = z.strictObject({ - executable_path: pathInputSchema, - application_path: pathInputSchema, - application_root: pathInputSchema.optional(), + executable_path: pathInputSchema.describe( + "Absolute local filesystem path for the Electron executable; relative paths are rejected.", + ), + application_path: pathInputSchema.describe( + "Absolute local filesystem path for the Electron application; relative paths are rejected.", + ), + application_root: pathInputSchema + .optional() + .describe( + "Absolute local filesystem root for application-relative paths; omit to derive it from the application path. Relative paths are rejected.", + ), args: z.array(z.string()).default([]), actions: z.array(actionSchema).default([]), }); diff --git a/src/domain/javascript/electronStaticAnalysisOptions.test.ts b/src/domain/javascript/electronStaticAnalysisOptions.test.ts index 16569d46f..024df7582 100644 --- a/src/domain/javascript/electronStaticAnalysisOptions.test.ts +++ b/src/domain/javascript/electronStaticAnalysisOptions.test.ts @@ -2,156 +2,46 @@ import { describe, expect, it } from "vitest"; import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; -describe.each(["webPreferences", "serviceName"] as const)( - "Electron %s option lookup", - (name) => { - const value = - name === "webPreferences" - ? '{ preload: "./kept.js", sandbox: true }' - : '"kept"'; - const explicit = `${name}: ${value}`; - const method = `${name}() { return ${value}; }`; - const getter = `get ${name}() { return ${value}; }`; - const setter = `set ${name}(value) {}`; +describe("Electron option evidence", () => { + it("keeps the final explicit preload and service name with unrelated keys", () => { + const analysis = analyzeJavaScriptStaticSource(` + new BrowserWindow({ webPreferences: { preload: "./kept.js", sandbox: true }, title: "App" }); + utilityProcess.fork("./worker.js", [], { serviceName: "kept", cwd: "/app" }); + `); - const explicitCases = [ - ["plain key", explicit], - ["quoted key", `"${name}": ${value}`], - ["computed string key", `["${name}"]: ${value}`], - ["duplicate key", `${name}: null, ${explicit}`], - ["computed duplicate key", `${name}: null, ["${name}"]: ${value}`], - ["earlier spread", `...options, ${explicit}`], - ["earlier computed identifier", `[key]: null, ${explicit}`], - ["earlier computed expression", `[getKey()]: null, ${explicit}`], - ["earlier getter", `${getter}, ${explicit}`], - ["earlier method", `${method}, ${explicit}`], - ["unrelated later key", `${explicit}, title: "title"`], - ["unrelated computed literal", `${explicit}, ["title"]: null`], - ["unrelated numeric key", `${explicit}, [1]: null`], - ["unrelated empty key", `${explicit}, [""]: null`], - ["unrelated getter", `${explicit}, get title() { return "title"; }`], - ]; - - it.each(explicitCases)( - "retains the explicit value with %s", - (_, members) => { - const analysis = analyzeOption(name, `{ ${members} }`); - expect(analysis.parse_status).toBe("complete"); - if (name === "webPreferences") { - expect(analysis.electron.browser_windows).toEqual([ - expect.objectContaining({ - options_status: "object-literal", - web_preferences_status: "object-literal", - preload_path: "./kept.js", - preload_resolution_context: "module-specifier", - web_preferences: [ - { name: "preload", value: literal("./kept.js") }, - { name: "sandbox", value: literal(true) }, - ], - }), - ]); - } else { - expect(analysis.electron.utility_processes).toEqual([ - expect.objectContaining({ service_name: "kept" }), - ]); - } - }, - ); - - const dynamicCases = [ - ["same-spelled computed binding", `[${name}]: ${value}`], - ["different computed binding", `[key]: ${value}`], - ["computed expression", `[getKey()]: ${value}`], - ["unevaluated template key", `[\`${name}\`]: ${value}`], - ["spread only", "...options"], - ["later spread", `${explicit}, ...options`], - ["later computed binding", `${explicit}, [key]: null`], - ["later computed expression", `${explicit}, [getKey()]: null`], - ["exact getter", getter], - ["exact setter", setter], - ["exact method", method], - ["computed string method", `["${name}"]() { return ${value}; }`], - ["overriding getter", `${explicit}, ${getter}`], - ["overriding setter", `${explicit}, ${setter}`], - ["overriding method", `${explicit}, ${method}`], - ["dynamic explicit value", `${name}: dynamicValue`], - ["null explicit value", `${name}: null`], - ["duplicate null value", `${explicit}, ${name}: null`], - ]; - - it.each(dynamicCases)("preserves uncertainty for %s", (_, members) => { - expectUnknownOption(name, `{ ${members} }`, "object-literal"); - }); - - it.each(["null", "options"])( - "preserves nonliteral options %s", - (options) => { - expectUnknownOption(name, options, "dynamic"); - }, - ); - - it.each(["", "{}", '{ title: "title", ["other"]: null }'])( - "reports an omitted option in %s without inventing a default", - (options) => { - const analysis = analyzeOption(name, options); - expect(analysis.parse_status).toBe("complete"); - if (name === "webPreferences") - expect(analysis.electron.browser_windows[0]).toMatchObject({ - options_status: options === "" ? "missing" : "object-literal", - web_preferences_status: "missing", - web_preferences: [], - preload_path: null, - preload_resolution_context: null, - }); - else - expect( - analysis.electron.utility_processes[0]?.service_name, - ).toBeNull(); - }, - ); - }, -); - -type OptionName = "webPreferences" | "serviceName"; - -const analyzeOption = (name: OptionName, options: string) => - analyzeJavaScriptStaticSource(` - const webPreferences = "title"; - const serviceName = "env"; - ${ - name === "webPreferences" - ? `new BrowserWindow(${options});` - : `utilityProcess.fork("./worker.js", []${options === "" ? "" : `, ${options}`});` - } - `); - -const literal = (value: string | boolean) => ({ - status: "literal", - value, - expression: null, -}); - -const expectUnknownOption = ( - name: OptionName, - options: string, - optionsStatus: "object-literal" | "dynamic", -): void => { - const analysis = analyzeOption(name, options); - expect(analysis.parse_status).toBe("partial"); - expect(analysis.parse_error_count).toBe(0); - expect(analysis.limitations.join(" ")).toContain("remain unknown"); - if (name === "webPreferences") + expect(analysis.parse_status).toBe("complete"); expect(analysis.electron.browser_windows[0]).toMatchObject({ - options_status: optionsStatus, - web_preferences_status: "dynamic", - web_preferences: [], - preload_path: null, - preload_resolution_context: null, + preload_path: "./kept.js", + preload_resolution_context: "module-specifier", + web_preferences: [ + { name: "preload", value: { status: "literal", value: "./kept.js" } }, + { name: "sandbox", value: { status: "literal", value: true } }, + ], + }); + expect(analysis.electron.utility_processes[0]).toMatchObject({ + module_path: "./worker.js", + service_name: "kept", + }); + }); + + it("keeps values unknown when a later computed key can override them", () => { + const analysis = analyzeJavaScriptStaticSource(` + new BrowserWindow({ webPreferences: { preload: "./maybe.js" }, [key]: null }); + utilityProcess.fork("./worker.js", [], { serviceName: "maybe", [key]: null }); + `); + + expect(analysis.parse_status).toBe("partial"); + expect(analysis.parse_error_count).toBe(0); + expect(analysis.limitations.join(" ")).toContain("remain unknown"); + expect(analysis.electron.browser_windows[0]).toMatchObject({ + preload_path: null, + preload_resolution_context: null, + web_preferences_status: "dynamic", + web_preferences: [], }); - else expect(analysis.electron.utility_processes[0]).toMatchObject({ module_path: "./worker.js", - module_expression: null, service_name: null, }); -}; + }); +}); diff --git a/src/domain/javascript/javascriptExportShapeComparison.ts b/src/domain/javascript/javascriptExportShapeComparison.ts index d8c8620fa..c991746bc 100644 --- a/src/domain/javascript/javascriptExportShapeComparison.ts +++ b/src/domain/javascript/javascriptExportShapeComparison.ts @@ -1,5 +1,8 @@ -import { uniqueSorted } from "../canonicalOrdering.js"; -import { digestExportShapeValue } from "./javascriptExportShapeComparisonIdentity.js"; +import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js"; +import { + canonicalExportShapeValue, + digestExportShapeValue, +} from "./javascriptExportShapeComparisonIdentity.js"; import { javaScriptExportShapeComparisonResultSchema, type JavaScriptExportShapeComparisonChange, @@ -14,7 +17,6 @@ import { } from "./javascriptExportShapeSelection.js"; import { buildJavaScriptExportShapeChanges, - compareJavaScriptExportShapeChanges, hasPartialJavaScriptExportPropertyCoverage, pairJavaScriptExportShapeVariants, type JavaScriptExportShapePairing, @@ -42,14 +44,16 @@ export const compareJavaScriptExportShapes = ( input.left.evidenceId, input.right.evidenceId, ); - const allChanges = buildJavaScriptExportShapeChanges({ - leftSelection: left, - rightSelection: right, - pairing, - leftShapes: leftRetained.shapes, - rightShapes: rightRetained.shapes, - evidenceLinks, - }).sort(compareJavaScriptExportShapeChanges); + const allChanges = sortChanges( + buildJavaScriptExportShapeChanges({ + leftSelection: left, + rightSelection: right, + pairing, + leftShapes: leftRetained.shapes, + rightShapes: rightRetained.shapes, + evidenceLinks, + }), + ); const changes = allChanges; const omittedChanges = 0; const coverage = comparisonCoverage({ @@ -78,6 +82,29 @@ export const compareJavaScriptExportShapes = ( }); }; +const sortChanges = ( + changes: readonly JavaScriptExportShapeComparisonChange[], +): JavaScriptExportShapeComparisonChange[] => + changes + .map((change, index) => ({ + change, + index, + sortKey: canonicalExportShapeValue({ + discriminant: change.discriminant, + path: change.path, + status: change.status, + left_source_range: change.left_source_range, + right_source_range: change.right_source_range, + change_id: change.change_id, + }), + })) + .sort( + (left, right) => + compareCodePoints(left.sortKey, right.sortKey) || + left.index - right.index, + ) + .map(({ change }) => change); + interface CoverageInput { readonly input: JavaScriptExportShapeComparisonProjectionInput; readonly left: SelectedJavaScriptExport; diff --git a/src/domain/javascript/javascriptExportShapeComparisonSchemas.test.ts b/src/domain/javascript/javascriptExportShapeComparisonSchemas.test.ts index acb1fe1f1..3ab413e85 100644 --- a/src/domain/javascript/javascriptExportShapeComparisonSchemas.test.ts +++ b/src/domain/javascript/javascriptExportShapeComparisonSchemas.test.ts @@ -1,9 +1,6 @@ import { expect, it } from "vitest"; -import { - javaScriptExportShapeComparisonResultSchema, - projectedExportReturnShapesSchema, -} from "./javascriptExportShapeComparisonSchemas.js"; +import { projectedExportReturnShapesSchema } from "./javascriptExportShapeComparisonSchemas.js"; const projectionWithField = (field: unknown) => ({ semantic_role: "export-return-shapes", @@ -54,104 +51,3 @@ it("parses projected fields into literal, union, or unknown values", () => { ).success, ).toBe(true); }); - -it("accepts every projected return site and field without presentation caps", () => { - const fields = Array.from({ length: 65 }, (_, index) => ({ - path: `/field-${String(index)}`, - state: "literal", - value: index, - reason: null, - })); - const propertyCoverage = fields.map(({ path }) => ({ - path, - status: "complete", - omitted: 0, - })); - const shapes = Array.from({ length: 33 }, () => ({ - source_range: { - start: { line: 1, column: 0 }, - end: { line: 1, column: 10 }, - }, - value_status: "object", - fields, - property_coverage: propertyCoverage, - })); - - expect( - projectedExportReturnShapesSchema.parse({ - ...projectionWithField(fields[0]), - static_return_shapes: shapes, - return_shape_coverage: { - status: "complete", - retained_return_sites: shapes.length, - omitted_return_sites: 0, - omitted_fields: 0, - omitted_property_coverage: 0, - projection_complete: true, - }, - }).static_return_shapes, - ).toHaveLength(33); -}); - -it("accepts complete candidate and change inventories without page ceilings", () => { - const evidenceId = `ev_${"a".repeat(64)}`; - const graphId = `jag_${"b".repeat(64)}`; - const selector = { - evidence_id: evidenceId, - graph_id: graphId, - requested_module_path: "index.js", - requested_export_name: "render", - candidates: Array.from({ length: 1_001 }, (_, index) => ({ - node_id: `jag_node_${index.toString(16).padStart(64, "0")}`, - module_path: "index.js", - export_name: "render", - matches_requested_module: true, - matches_requested_export: true, - })), - omitted_candidates: 0, - status: "missing", - selected_node_id: null, - }; - const changes = Array.from({ length: 10_001 }, (_, index) => ({ - change_id: `jesc_change_${index.toString(16).padStart(64, "0")}`, - status: "unknown", - path: `/field-${String(index)}`, - discriminant: null, - left: { availability: "absent" }, - right: { availability: "absent" }, - left_source_range: null, - right_source_range: null, - evidence_links: [evidenceId, `ev_${"c".repeat(64)}`], - limitations: [], - })); - - expect( - javaScriptExportShapeComparisonResultSchema.parse({ - comparison_id: `jesc_${"d".repeat(64)}`, - left: selector, - right: { ...selector, graph_id: `jag_${"e".repeat(64)}` }, - summary: { added: 0, removed: 0, changed: 0, unknown: changes.length }, - changes, - coverage: { - status: "complete-within-inputs", - left_graph_status: "complete", - right_graph_status: "complete", - paired_variants: 0, - unpaired_left_variants: 0, - unpaired_right_variants: 0, - omitted_left_variants: 0, - omitted_right_variants: 0, - left_source_omitted_variants: 0, - right_source_omitted_variants: 0, - left_omitted_fields: 0, - right_omitted_fields: 0, - left_omitted_property_coverage: 0, - right_omitted_property_coverage: 0, - omitted_candidates: 0, - omitted_changes: 0, - }, - evidence_links: [evidenceId, `ev_${"c".repeat(64)}`], - limitations: [], - }).changes, - ).toHaveLength(10_001); -}); diff --git a/src/domain/javascript/javascriptExportShapeVariants.ts b/src/domain/javascript/javascriptExportShapeVariants.ts index 00c420c44..e5880aca6 100644 --- a/src/domain/javascript/javascriptExportShapeVariants.ts +++ b/src/domain/javascript/javascriptExportShapeVariants.ts @@ -112,16 +112,6 @@ export const buildJavaScriptExportShapeChanges = ( return [...paired, ...unpairedLeft, ...unpairedRight]; }; -/** Sort changes by semantic content rather than traversal order. */ -export const compareJavaScriptExportShapeChanges = ( - left: JavaScriptExportShapeComparisonChange, - right: JavaScriptExportShapeComparisonChange, -): number => - compareCodePoints( - canonicalExportShapeValue(changeSortKey(left)), - canonicalExportShapeValue(changeSortKey(right)), - ); - /** Report whether any retained shape lacks complete property coverage. */ export const hasPartialJavaScriptExportPropertyCoverage = ( shapes: readonly Shape[], @@ -429,15 +419,6 @@ const changeWithId = ( change_id: `jesc_change_${digestExportShapeValue(semantic)}`, }); -const changeSortKey = (change: JavaScriptExportShapeComparisonChange) => ({ - discriminant: change.discriminant, - path: change.path, - status: change.status, - left_source_range: change.left_source_range, - right_source_range: change.right_source_range, - change_id: change.change_id, -}); - const literalValue = ( field: Field, ): diff --git a/src/domain/javascript/javascriptFeatureSeed.array_members.test.ts b/src/domain/javascript/javascriptFeatureSeed.array_members.test.ts index bc3828fb5..15a6f8445 100644 --- a/src/domain/javascript/javascriptFeatureSeed.array_members.test.ts +++ b/src/domain/javascript/javascriptFeatureSeed.array_members.test.ts @@ -253,56 +253,58 @@ describe("feature seed selection controls", () => { }); describe("public feature trace from contextBridge source members", () => { - it.each(matchingModes)( - "traces $match / $value / case-sensitive $case_sensitive", - (mode) => { - const graph = contextBridgeGraph(); - const before = structuredClone(graph); - const input = { - sourceEvidenceId, - graph, - nativeEvidence: [], - direction: "both" as const, - }; - const generic = traceApplicationFeature({ - ...input, - seed: { kind: "string", ...mode }, - }); - const typed = traceApplicationFeature({ - ...input, - seed: { kind: "api", ...mode }, - }); + it("traces one exact member seed through the public feature workflow", () => { + const mode = { + value: "openProject", + match: "exact" as const, + case_sensitive: true, + }; + const graph = contextBridgeGraph(); + const before = structuredClone(graph); + const input = { + sourceEvidenceId, + graph, + nativeEvidence: [], + direction: "both" as const, + }; + const generic = traceApplicationFeature({ + ...input, + seed: { kind: "string", ...mode }, + }); + const typed = traceApplicationFeature({ + ...input, + seed: { kind: "api", ...mode }, + }); - expect(typed.coverage).toEqual({ - status: "complete-within-source", - source_graph_status: "complete", - total_seed_matches: 1, - }); - expect(typed.seed_matches).toEqual(generic.seed_matches); - expect(typed.seed_matches).toEqual([ - { - node_id: graph.nodes[0]?.node_id, - kind: "context-bridge-api", - basis: "property", - field: "observations[0].properties.members[1]", - }, - ]); - expect(typed.graph?.nodes).toEqual(graph.nodes); - expect(typed.graph?.edges).toEqual([]); - expect(typed.evidence_links).toEqual([sourceEvidenceId]); - expect(typed.summary).toMatchObject({ - matched_seeds: 1, - traced_nodes: 1, - traced_edges: 0, - observed_facts: 1, - inferred_facts: 0, - unknown_facts: 0, - unavailable_facts: 0, - }); - expect(typed.native_handoffs).toEqual([]); - expect(graph).toEqual(before); - }, - ); + expect(typed.coverage).toEqual({ + status: "complete-within-source", + source_graph_status: "complete", + total_seed_matches: 1, + }); + expect(typed.seed_matches).toEqual(generic.seed_matches); + expect(typed.seed_matches).toEqual([ + { + node_id: graph.nodes[0]?.node_id, + kind: "context-bridge-api", + basis: "property", + field: "observations[0].properties.members[1]", + }, + ]); + expect(typed.graph?.nodes).toEqual(graph.nodes); + expect(typed.graph?.edges).toEqual([]); + expect(typed.evidence_links).toEqual([sourceEvidenceId]); + expect(typed.summary).toMatchObject({ + matched_seeds: 1, + traced_nodes: 1, + traced_edges: 0, + observed_facts: 1, + inferred_facts: 0, + unknown_facts: 0, + unavailable_facts: 0, + }); + expect(typed.native_handoffs).toEqual([]); + expect(graph).toEqual(before); + }); it("keeps a missing member explicit without changing source coverage", () => { const graph = contextBridgeGraph(); diff --git a/src/domain/javascript/javascriptRecovery.test.ts b/src/domain/javascript/javascriptRecovery.test.ts index 5f9a2b687..4a48a44b0 100644 --- a/src/domain/javascript/javascriptRecovery.test.ts +++ b/src/domain/javascript/javascriptRecovery.test.ts @@ -2,19 +2,6 @@ import { describe, expect, it } from "vitest"; import { javascriptRecoveryInputSchema } from "./javascriptRecovery.js"; describe("JavaScript recovery requests", () => { - it("defaults omitted transform metadata without requiring engine confirmations", () => { - expect( - javascriptRecoveryInputSchema.parse({ - path: "/tmp/bundle.js", - output_directory: "/tmp/recovered", - }), - ).toEqual({ - path: "/tmp/bundle.js", - output_directory: "/tmp/recovered", - extraction_mode: "structural", - rewrite_level: "standard", - }); - }); it.each([ { path: "", output_directory: "/tmp/recovered" }, { path: "/tmp/bundle.js" }, diff --git a/src/domain/javascript/javascriptSemanticAnalysis.builtin_defaults.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.builtin_defaults.test.ts index 0e4b2c22a..15a4619c3 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.builtin_defaults.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.builtin_defaults.test.ts @@ -1,39 +1,15 @@ import { expect, it } from "vitest"; + import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js"; -it.each(["namespace", "default", "named-default"])( - "retains Node built-in effects from %s imports", - (mode) => { - const imports = ["fs", "child_process", "timers", "http"] - .map((name, index) => { - const declaration = - mode === "namespace" - ? `* as m${String(index)}` - : mode === "default" - ? `m${String(index)}` - : `{default as m${String(index)}}`; - return `import ${declaration} from 'node:${name}';`; - }) - .join("\n"); - const ir = analyzeJavaScriptSemantics(`${imports} - const text = m0.readFileSync('config.json','utf8'); - const stream = m0.createReadStream('config.json'); stream.destroy(); - const child = m1.spawn('/usr/bin/tool', ['--version']); - const timer = m2.setTimeout(work,1000); m2.clearTimeout(timer); - const request = m3.get('http://localhost/'); +it("does not assign built-in APIs to unrelated or shadowed bindings", () => { + const ir = analyzeJavaScriptSemantics(` + import fs from "./fs.js"; + import { constants as constantsFs } from "node:fs"; + fs.createReadStream("config.json"); + constantsFs.createReadStream("config.json"); + function run(fs) { fs.createReadStream("config.json"); } `); - expect(ir.configurationOperations).toHaveLength(1); - expect(ir.resourceOperations).toHaveLength(2); - expect(ir.childProcessSpawns).toHaveLength(1); - expect(ir.timerOperations).toHaveLength(2); - expect(ir.requestOperations).toHaveLength(1); - }, -); -it.each([ - "import fs from './fs.js'; fs.createReadStream('config.json');", - "import {constants as fs} from 'node:fs'; fs.createReadStream('config.json');", - "function run(fs) { fs.createReadStream('config.json'); }", -])("does not invent a built-in namespace for %s", (source) => { - expect(analyzeJavaScriptSemantics(source).resourceOperations).toEqual([]); + expect(ir.resourceOperations).toEqual([]); }); diff --git a/src/domain/javascript/javascriptSemanticAnalysis.calls.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.calls.test.ts index 78fea9845..880aae06c 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.calls.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.calls.test.ts @@ -155,19 +155,6 @@ describe("JavaScript semantic analysis: calls 2", () => { ]); }); - it("retains every promise operation found in the finite source", () => { - const ir = analyzeJavaScriptSemantics( - ` - Promise.resolve(1); - Promise.resolve(2); - Promise.resolve(3); - `, - ); - - expect(ir.promiseOperations).toHaveLength(3); - expect(ir.coverage.status).toBe("complete"); - }); - it("fingerprints formatting and local-name changes identically", () => { const left = analyzeJavaScriptSemantics(` function calculate(value) { diff --git a/src/domain/javascript/javascriptSemanticAnalysis.member_depth.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.member_depth.test.ts index 9d27e0ed5..e9f8d8159 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.member_depth.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.member_depth.test.ts @@ -1,67 +1,43 @@ import * as t from "@babel/types"; import { expect, it } from "vitest"; -import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js"; +import { + analyzeJavaScriptSemantics, + analyzeParsedJavaScriptSemantics, +} from "./javascriptSemanticAnalysis.js"; import { onlyBinding, origin } from "./javascriptSemanticAnalysis.fixture.js"; import { parseJavaScriptSource } from "./javascriptSourceParser.js"; import { calleeName } from "./javascriptStaticAnalysisHelpers.js"; -import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; +import { analyzeParsedJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; const memberChain = ".next".repeat(12_000); +const ordinaryMemberChain = ".next".repeat(64); -it("retains native addon observations through a deep member initializer", () => { - const analysis = analyzeJavaScriptStaticSource( - `const value = require("./addon.node")${memberChain}.last[""];`, +it("preserves a 12,000-member native import through both analyzers", () => { + const parsed = parseJavaScriptSource( + `const value = require("./addon.node")${memberChain}.last; const ordinary = root.next.last;`, ); - expect(analysis.electron.native_addon_bindings).toEqual([ + if (parsed === null) throw new Error("Expected valid JavaScript"); + + const staticAnalysis = analyzeParsedJavaScriptStaticSource("", parsed); + const ir = analyzeParsedJavaScriptSemantics(parsed); + expect(staticAnalysis.parse_status).toBe("complete"); + expect(staticAnalysis.electron.native_addon_bindings).toEqual([ expect.objectContaining({ specifier: "./addon.node", members: ["last"] }), ]); - expect(analysis.parse_error_count).toBe(0); -}); - -it("keeps a deep non-native initializer outside native addon observations", () => { - const analysis = analyzeJavaScriptStaticSource( - `const value = root${memberChain}.last;`, - ); - expect(analysis.electron.native_addon_bindings).toEqual([]); - expect(analysis.parse_status).toBe("complete"); -}); - -it("retains unknown value diagnostics and references after a deep member initializer", () => { - const ir = analyzeJavaScriptSemantics( - `const value = root${memberChain}.last;`, - ); expect(ir.coverage).toEqual({ status: "complete", omittedCount: 0 }); - expect(onlyBinding(ir, "value")).toMatchObject({ - value: { status: "unknown", reason: "Cannot project last from unknown." }, - provenance: { status: "unknown", reason: "Unbound identifier root." }, - }); - expect(ir.references.map(({ name, role }) => ({ name, role }))).toEqual([ - { name: "root", role: "read" }, - ]); -}); - -it.each([ - `const value = require("./dependency.js")${memberChain}[""].last;`, - `const dependency = require("./dependency.js"); const value = dependency${memberChain}[""].last;`, - `const value = require("./dependency.js")?.next${memberChain}[""].last;`, -])("preserves the full literal module path (case %#)", (source) => { - const ir = analyzeJavaScriptSemantics(source); const path = origin(onlyBinding(ir, "value")); - expect(path.specifier).toBe("./dependency.js"); + expect(path.specifier).toBe("./addon.node"); expect(path.importedPath).toEqual([ - ...(source.includes("?.next") ? ["next"] : []), ...Array.from({ length: 12_000 }, () => "next"), - "", "last", ]); - expect(ir.coverage.status).toBe("complete"); }); it("retains a deep CommonJS re-export path without inventing dynamic module links", () => { const ir = analyzeJavaScriptSemantics(` - module.exports.result = require("./dependency.js")${memberChain}.last; - const dynamic = require("./other.js")${memberChain}[key].last; + module.exports.result = require("./dependency.js")${ordinaryMemberChain}.last; + const dynamic = require("./other.js")${ordinaryMemberChain}[key].last; `); expect(ir.moduleLinks).toEqual([ expect.objectContaining({ @@ -80,7 +56,7 @@ it("retains a deep CommonJS re-export path without inventing dynamic module link it("invalidates a deep mutation while retaining unrelated literal properties", () => { const ir = analyzeJavaScriptSemantics(` const root = { untouched: "retained" }; - root${memberChain}.last = 1; + root${ordinaryMemberChain}.last = 1; const value = root.untouched; `); expect(onlyBinding(ir, "root").value).toEqual({ @@ -98,7 +74,7 @@ it("invalidates a deep mutation while retaining unrelated literal properties", ( }); it("retains the complete location and unresolved target of a deep call", () => { - const callee = `root${memberChain}.last`; + const callee = `root${ordinaryMemberChain}.last`; const ir = analyzeJavaScriptSemantics(`${callee}();`); expect(ir.callSites).toEqual([ expect.objectContaining({ @@ -115,7 +91,7 @@ it("retains the complete location and unresolved target of a deep call", () => { it("retains deep default values without confusing their reads with parameter bindings", () => { const ir = analyzeJavaScriptSemantics( - `export function inspect(value = root${memberChain}.last) { return value; }`, + `export function inspect(value = root${ordinaryMemberChain}.last) { return value; }`, ); expect(ir.references.map(({ name, role }) => ({ name, role }))).toEqual([ { name: "root", role: "read" }, @@ -126,7 +102,7 @@ it("retains deep default values without confusing their reads with parameter bin it("keeps a shadowed require local through a deep member initializer", () => { const ir = analyzeJavaScriptSemantics( - `function inspect(require) { const value = require("./dependency.js")${memberChain}.last; return value; }`, + `function inspect(require) { const value = require("./dependency.js")${ordinaryMemberChain}.last; return value; }`, ); expect(ir.moduleLinks).toEqual([]); expect(onlyBinding(ir, "value").provenance.origins).toEqual([]); @@ -140,7 +116,7 @@ it.each([ ["root[0].last()", "root.0.last"], ["root[key].last()", "root.[computed@5].last"], ["root?.first.last()", "root.first.last"], - [`root${memberChain}.last()`, `root${memberChain}.last`], + [`root${ordinaryMemberChain}.last()`, `root${ordinaryMemberChain}.last`], ])("preserves exact callee syntax (case %#)", (source, expected) => { const file = parseJavaScriptSource(source); const statement = file?.program.body[0]; diff --git a/src/domain/javascript/javascriptSemanticAnalysis.promise_ownership.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.promise_ownership.test.ts index 66d484506..58b572a96 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.promise_ownership.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.promise_ownership.test.ts @@ -147,3 +147,54 @@ it("keeps an explicit return statement's full range and the producer's location" }); expect(site.location.start.column).toBe(source.indexOf("return")); }); + +it("links returned promises to the exact return site in each callable", () => { + const ir = analyzeJavaScriptSemantics( + [ + "function first() { return Promise.resolve(1); }", + "function second() { return Promise.resolve(2); }", + ].join("\n"), + ); + const callables = new Map( + ir.callables.map((callable) => [callable.name, callable]), + ); + const promises = ir.promiseOperations.filter( + ({ method }) => method === "resolve", + ); + + expect(promises).toHaveLength(2); + for (const name of ["first", "second"] as const) { + const callable = callables.get(name); + const promise = promises.find( + ({ ownerCallableId }) => ownerCallableId === callable?.callableId, + ); + expect(callable?.returnSites).toHaveLength(1); + expect(promise).toMatchObject({ + ownership: "returned", + ownerCallableId: callable?.callableId, + returnSiteId: callable?.returnSites[0]?.returnSiteId, + }); + } +}); + +it("does not assign a return site to a Promise owned by a binding", () => { + const ir = analyzeJavaScriptSemantics( + "function unrelated() { return Promise.resolve(2); }\nconst pending = Promise.resolve(1);", + ); + const owned = ir.promiseOperations.find( + ({ ownerCallableId }) => ownerCallableId !== null, + ); + const assigned = ir.promiseOperations.find( + ({ ownerCallableId }) => ownerCallableId === null, + ); + + expect(owned).toMatchObject({ + ownership: "returned", + returnSiteId: ir.callables.find(({ name }) => name === "unrelated") + ?.returnSites[0]?.returnSiteId, + }); + expect(assigned).toMatchObject({ + ownership: "assigned", + returnSiteId: null, + }); +}); diff --git a/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts index 25e02aefa..e6a06e728 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts @@ -18,6 +18,19 @@ const valueOf = (expression: string) => analyzeJavaScriptSemantics(`const answer = ${expression};`), "answer", ).value; +const valuesOf = (expressions: readonly string[]) => { + const ir = analyzeJavaScriptSemantics( + expressions + .map( + (expression, index) => + `const candidate${String(index)} = ${expression};`, + ) + .join("\n"), + ); + return expressions.map( + (_, index) => topLevelBinding(ir, `candidate${String(index)}`).value, + ); +}; describe("conditional primitive unions", () => { const cases = conditionalCandidates.flatMap((known) => @@ -35,11 +48,11 @@ describe("conditional primitive unions", () => { ]), ); - it.each(cases)("preserves incompatible alternatives in %s", (expression) => { - expect(valueOf(expression)).toEqual(incompatibleValue); + it("preserves incompatible alternatives across conditional expressions", () => { + expect(valuesOf(cases)).toEqual(cases.map(() => incompatibleValue)); }); - it.each([ + const exactCases = [ [ 'condition ? (choice ? "two" : "one") : "three"', { status: "union", values: ["one", "three", "two"] }, @@ -69,8 +82,12 @@ describe("conditional primitive unions", () => { { status: "union", values: [null, "one", "two"] }, ], ["condition ? null : null", { status: "literal", value: null }], - ])("retains exact primitive candidates in %s", (expression, expected) => { - expect(valueOf(expression)).toEqual(expected); + ] as const; + + it("retains exact primitive candidates across conditional expressions", () => { + expect(valuesOf(exactCases.map(([expression]) => expression))).toEqual( + exactCases.map(([, expected]) => expected), + ); }); it("propagates an incompatible nested branch through an outer union", () => { @@ -119,11 +136,11 @@ describe("logical primitive unions", () => { ), ); - it.each(cases)("preserves unresolved alternatives in %s", (expression) => { - expect(valueOf(expression)).toEqual(incompatibleValue); + it("preserves unresolved alternatives across logical expressions", () => { + expect(valuesOf(cases)).toEqual(cases.map(() => incompatibleValue)); }); - it.each([ + const exactCases = [ [ '(choice ? "" : "one") || "two"', { status: "union", values: ["", "one", "two"] }, @@ -138,19 +155,24 @@ describe("logical primitive unions", () => { ], ['"one" || "one"', { status: "literal", value: "one" }], ["null ?? null", { status: "literal", value: null }], - ])( - "retains the conservative primitive union in %s", - (expression, expected) => { - expect(valueOf(expression)).toEqual(expected); - }, - ); + ] as const; - it.each([ + it("retains conservative primitive unions across logical expressions", () => { + expect(valuesOf(exactCases.map(([expression]) => expression))).toEqual( + exactCases.map(([, expected]) => expected), + ); + }); + + const unevaluatedCases = [ 'true ? "one" : missing', '"one" || missing', "false && missing", '"one" ?? missing', - ])("does not introduce short-circuit evaluation for %s", (expression) => { - expect(valueOf(expression)).toEqual(incompatibleValue); + ]; + + it("does not introduce short-circuit evaluation", () => { + expect(valuesOf(unevaluatedCases)).toEqual( + unevaluatedCases.map(() => incompatibleValue), + ); }); }); diff --git a/src/domain/javascript/javascriptSemanticPromises.ts b/src/domain/javascript/javascriptSemanticPromises.ts index 873d3491e..31510e6c8 100644 --- a/src/domain/javascript/javascriptSemanticPromises.ts +++ b/src/domain/javascript/javascriptSemanticPromises.ts @@ -15,7 +15,7 @@ import { } from "./javascriptSemanticState.js"; import { compareCodePoints } from "../canonicalOrdering.js"; import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js"; -import { range, sourceRangesEqual } from "./javascriptStaticAnalysisHelpers.js"; +import { range } from "./javascriptStaticAnalysisHelpers.js"; import { assignedSemanticResultBindings, dataEffectMemberCallee, @@ -56,7 +56,10 @@ interface PromiseOwnershipContext { readonly candidate: PromiseCandidate; readonly candidateByNode: WeakMap; readonly state: JavaScriptSemanticAnalysisState; - readonly callables: readonly JavaScriptSemanticCallable[]; + readonly returnSiteIdFor: ( + callableId: string | null, + location: JavaScriptSemanticPromiseOperation["location"], + ) => string | null; } /** Recover explicit Promise construction, chaining, aggregation, and ownership. */ @@ -66,6 +69,7 @@ export const collectJavaScriptSemanticPromises = ( callables: readonly JavaScriptSemanticCallable[], ): JavaScriptSemanticPromiseOperation[] => { const candidates = collectCandidates(program, state, callables); + const returnSiteIdFor = createReturnSiteLookup(callables); const candidateByNode = new WeakMap(); for (const candidate of candidates) candidateByNode.set(candidate.node, candidate); @@ -74,7 +78,7 @@ export const collectJavaScriptSemanticPromises = ( candidate, candidateByNode, state, - callables, + returnSiteIdFor, ); const sources = promiseSources(candidate, candidateByNode, state); return { @@ -180,14 +184,14 @@ const promiseOwnership = ( candidate: PromiseCandidate, candidateByNode: WeakMap, state: JavaScriptSemanticAnalysisState, - callables: readonly JavaScriptSemanticCallable[], + returnSiteIdFor: PromiseOwnershipContext["returnSiteIdFor"], ): PromiseOwnership => { if (candidate.kind === "awaited-expression") return emptyOwnership("awaited"); const context: PromiseOwnershipContext = { candidate, candidateByNode, state, - callables, + returnSiteIdFor, }; for (let index = candidate.ancestors.length - 1; index >= 0; index -= 1) { const ancestor = candidate.ancestors[index]; @@ -203,7 +207,7 @@ const ownershipAtAncestor = ( ancestor: t.Node, context: PromiseOwnershipContext, ): PromiseOwnership | "boundary" | null => { - const { candidate, candidateByNode, state, callables } = context; + const { candidate, candidateByNode, state, returnSiteIdFor } = context; if (t.isAwaitExpression(ancestor)) return unwrapExpression(ancestor.argument) === candidate.node ? emptyOwnership("awaited") @@ -234,7 +238,7 @@ const ownershipAtAncestor = ( return returnedPromiseOwnership( candidate.ownerCallableId, range(ancestor), - callables, + returnSiteIdFor, ); } if (t.isExpressionStatement(ancestor)) @@ -249,7 +253,7 @@ const ownershipAtAncestor = ( ? returnedPromiseOwnership( candidate.ownerCallableId, range(ancestor.body), - callables, + returnSiteIdFor, ) : "boundary"; }; @@ -283,11 +287,11 @@ const assignedPromiseBinding = ( const returnedPromiseOwnership = ( callableId: string | null, location: JavaScriptSemanticPromiseOperation["location"], - callables: readonly JavaScriptSemanticCallable[], + returnSiteIdFor: PromiseOwnershipContext["returnSiteIdFor"], ): PromiseOwnership => ({ ownership: "returned", ownerBindingId: null, - returnSiteId: matchingReturnSite(callableId, location, callables), + returnSiteId: returnSiteIdFor(callableId, location), }); const outerConsumesCandidate = ( @@ -329,16 +333,56 @@ const emptyOwnership = ( returnSiteId: null, }); -const matchingReturnSite = ( - callableId: string | null, - location: JavaScriptSemanticPromiseOperation["location"], +const indexReturnSites = ( + callable: JavaScriptSemanticCallable, +): ReadonlyMap => { + const output = new Map(); + for (const site of callable.returnSites) { + const key = returnSiteKey(site.location); + if (!output.has(key)) output.set(key, site.returnSiteId); + } + return output; +}; + +const createReturnSiteLookup = ( callables: readonly JavaScriptSemanticCallable[], -): string | null => - callables - .find(({ callableId: candidate }) => candidate === callableId) - ?.returnSites.find(({ location: candidate }) => - sourceRangesEqual(candidate, location), - )?.returnSiteId ?? null; +): PromiseOwnershipContext["returnSiteIdFor"] => { + let callablesById: + | ReadonlyMap + | undefined; + let returnSiteIdsByCallable: + | Map> + | undefined; + return (callableId, location) => { + if (callableId === null) return null; + callablesById ??= indexCallablesById(callables); + const callable = callablesById.get(callableId); + if (callable === undefined) return null; + returnSiteIdsByCallable ??= new Map(); + let returnSiteIds = returnSiteIdsByCallable.get(callableId); + if (returnSiteIds === undefined) { + returnSiteIds = indexReturnSites(callable); + returnSiteIdsByCallable.set(callableId, returnSiteIds); + } + return returnSiteIds.get(returnSiteKey(location)) ?? null; + }; +}; + +const indexCallablesById = ( + callables: readonly JavaScriptSemanticCallable[], +): ReadonlyMap => { + const output = new Map(); + for (const callable of callables) { + if (output.has(callable.callableId)) continue; + output.set(callable.callableId, callable); + } + return output; +}; + +const returnSiteKey = ( + location: JavaScriptSemanticPromiseOperation["location"], +): string => + `${location.start.line}:${location.start.column}:${location.end.line}:${location.end.column}`; const promiseSources = ( candidate: PromiseCandidate, diff --git a/src/domain/javascript/javascriptSemanticQuerySchemas.test.ts b/src/domain/javascript/javascriptSemanticQuerySchemas.test.ts index a4f334163..ab64cfe00 100644 --- a/src/domain/javascript/javascriptSemanticQuerySchemas.test.ts +++ b/src/domain/javascript/javascriptSemanticQuerySchemas.test.ts @@ -1,28 +1,8 @@ import { expect, it } from "vitest"; -import { - javaScriptSemanticQueryInputSchema, - javaScriptSemanticQueryResultSchema, -} from "./javascriptSemanticQuerySchemas.js"; +import { javaScriptSemanticQueryInputSchema } from "./javascriptSemanticQuerySchemas.js"; -it("accepts long selectors and complete relation and class selections", () => { - const longText = "x".repeat(16_385); - const result = javaScriptSemanticQueryInputSchema.parse({ - seed: { kind: "endpoint", value: longText }, - direction: "forward-influence", - allowed_relations: Array(2).fill("calls"), - expected: { - role: "sink", - classes: Array(25).fill("module"), - }, - }); - - expect(result.seed).toEqual({ kind: "endpoint", value: longText }); - expect(result.allowed_relations).toHaveLength(2); - expect(result.expected?.classes).toHaveLength(25); -}); - -it("rejects the inert source-map authority setting", () => { +it("rejects a source-map authority setting that the query does not consume", () => { expect( javaScriptSemanticQueryInputSchema.safeParse({ seed: { kind: "endpoint", value: "/api" }, @@ -31,36 +11,3 @@ it("rejects the inert source-map authority setting", () => { }).success, ).toBe(false); }); - -it("accepts every reported limitation without a count ceiling", () => { - const limitations = Array.from( - { length: 1_001 }, - (_, index) => `gap-${index}`, - ); - const id = "a".repeat(64); - - const result = javaScriptSemanticQueryResultSchema.parse({ - query_id: `jsrq_${id}`, - source_graph_id: `jsrg_${id}`, - seed: { kind: "literal", value: null }, - direction: "forward-influence", - status: "no-match", - seed_node_ids: [], - nodes: [], - relations: [], - unknowns: [], - expected_match_node_ids: [], - summary: { - total_seed_matches: 0, - traversed_nodes: 0, - traversed_relations: 0, - traversed_functions: 0, - traversed_modules: 0, - relevant_unknowns: 0, - }, - coverage: { status: "complete" }, - limitations, - }); - - expect(result.limitations).toHaveLength(1_001); -}); diff --git a/src/domain/javascript/javascriptSemanticTraversal.test.ts b/src/domain/javascript/javascriptSemanticTraversal.test.ts deleted file mode 100644 index 482afe2b9..000000000 --- a/src/domain/javascript/javascriptSemanticTraversal.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as t from "@babel/types"; -import { expect, it } from "vitest"; - -import { parseJavaScriptSource } from "./javascriptSourceParser.js"; -import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js"; - -it("preserves source-tree enter/exit order and detached ancestor snapshots", () => { - const parsed = parseJavaScriptSource("const answer = first + second;"); - if (parsed === null) - throw new TypeError("Expected a parsed JavaScript fixture"); - const visits: string[] = []; - const snapshots: (readonly t.Node[])[] = []; - traverseJavaScriptAst(parsed.program, { - enter: (node, parent, readAncestors) => { - visits.push(`enter:${node.type}`); - const ancestors = readAncestors(); - expect(ancestors.at(-1) ?? null).toBe(parent); - if (t.isIdentifier(node)) snapshots.push(ancestors); - }, - exit: (node) => visits.push(`exit:${node.type}`), - }); - expect(visits).toEqual([ - "enter:Program", - "enter:VariableDeclaration", - "enter:VariableDeclarator", - "enter:Identifier", - "exit:Identifier", - "enter:BinaryExpression", - "enter:Identifier", - "exit:Identifier", - "enter:Identifier", - "exit:Identifier", - "exit:BinaryExpression", - "exit:VariableDeclarator", - "exit:VariableDeclaration", - "exit:Program", - ]); - expect(snapshots.map((nodes) => nodes.map(({ type }) => type))).toEqual([ - ["Program", "VariableDeclaration", "VariableDeclarator"], - [ - "Program", - "VariableDeclaration", - "VariableDeclarator", - "BinaryExpression", - ], - [ - "Program", - "VariableDeclaration", - "VariableDeclarator", - "BinaryExpression", - ], - ]); -}); - -it("skips sparse child slots without disturbing sibling visit order", () => { - const parsed = parseJavaScriptSource("const values = [first, , second];"); - if (parsed === null) - throw new TypeError("Expected a parsed JavaScript fixture"); - const identifiers: string[] = []; - traverseJavaScriptAst(parsed.program, { - enter: (node) => { - if (t.isIdentifier(node)) identifiers.push(node.name); - }, - }); - expect(identifiers).toEqual(["values", "first", "second"]); -}); diff --git a/src/domain/javascript/javascriptStaticAnalysis.member_path_depth.test.ts b/src/domain/javascript/javascriptStaticAnalysis.member_path_depth.test.ts index 6e426c7c6..3d0313301 100644 --- a/src/domain/javascript/javascriptStaticAnalysis.member_path_depth.test.ts +++ b/src/domain/javascript/javascriptStaticAnalysis.member_path_depth.test.ts @@ -5,6 +5,7 @@ import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; import { parseJavaScriptSource } from "./javascriptSourceParser.js"; const memberChain = ".next".repeat(12_000); +const ordinaryMemberChain = ".next".repeat(64); it("collects a 12,000-member CommonJS export path without recursion", () => { const file = parseJavaScriptSource(`exports${memberChain}.last = 1;`); @@ -48,8 +49,8 @@ it("keeps left-first and outer-member-first bundler runtime matches", () => { ).toBe("webpackChunkInner"); }); -it("analyzes a 12,000-member bundler runtime chain without recursion", () => { - const source = `globalThis.webpackChunkApp${memberChain}.push([[1], { 1: function(module) { module.exports.ok = 42; } }]);`; +it("recognizes a bundler runtime through nested member names", () => { + const source = `globalThis.webpackChunkApp${ordinaryMemberChain}.push([[1], { 1: function(module) { module.exports.ok = 42; } }]);`; const analysis = analyzeJavaScriptStaticSource(source); expect(analysis.parse_status).toBe("complete"); expect(analysis.parse_error_count).toBe(0); diff --git a/src/domain/javascript/javascriptStaticAnalysisSourceMaps.test.ts b/src/domain/javascript/javascriptStaticAnalysisSourceMaps.test.ts index d9ae95380..93a9ddec9 100644 --- a/src/domain/javascript/javascriptStaticAnalysisSourceMaps.test.ts +++ b/src/domain/javascript/javascriptStaticAnalysisSourceMaps.test.ts @@ -22,12 +22,6 @@ describe("source map directives", () => { expect(sourceMapUrls(source)).toEqual([expected]); }); - it("keeps the comment terminator out of the declared url", () => { - expect(sourceMapUrls("/*# sourceMappingURL=app.js.map */")).toEqual([ - "app.js.map", - ]); - }); - it("retains every directive in a source", () => { expect( sourceMapUrls( @@ -84,30 +78,6 @@ describe("source map comment boundaries", () => { }); describe("source map comment locations and recovery", () => { - it("keeps exact locations and the first occurrence of duplicate URLs", () => { - const line = "//# sourceMappingURL=first.map"; - const block = "/*# sourceMappingURL=second.map"; - const result = analyzeJavaScriptStaticSource( - ['const marker = "😀";', line, `${block} */`, line].join("\r\n"), - ); - expect(result.source_map_urls).toEqual([ - { - declared_url: "first.map", - location: { - start: { line: 2, column: 0 }, - end: { line: 2, column: line.length }, - }, - }, - { - declared_url: "second.map", - location: { - start: { line: 3, column: 0 }, - end: { line: 3, column: block.length }, - }, - }, - ]); - }); - it("keeps real comments when the parser recovers and reports partial syntax", () => { const result = analyzeJavaScriptStaticSource( "let value; let value;\n//# sourceMappingURL=recovered.map", diff --git a/src/domain/jsonValue.test.ts b/src/domain/jsonValue.test.ts new file mode 100644 index 000000000..217c44626 --- /dev/null +++ b/src/domain/jsonValue.test.ts @@ -0,0 +1,37 @@ +import { Ajv2020 } from "ajv/dist/2020.js"; +import { expect, it } from "vitest"; +import { z } from "zod"; + +import { jsonObjectSchema, jsonValueSchema } from "./jsonValue.js"; + +const values = [ + "text", + 0, + false, + null, + [], + {}, + { nested: [{ deeper: [1, "two", null, { three: true }] }] }, +]; + +it("projects JSON values without recursive definitions", () => { + for (const io of ["input", "output"] as const) { + const schema = z.toJSONSchema( + z.object({ value: jsonValueSchema, map: jsonObjectSchema }), + { io }, + ); + expect(JSON.stringify(schema)).not.toContain("$ref"); + expect(schema).not.toHaveProperty("$defs"); + const validate = new Ajv2020({ strict: false }).compile(schema); + for (const value of values) + expect(validate({ value, map: { value } })).toBe(true); + expect(validate({ value: 1, map: [] })).toBe(false); + } +}); + +it("still parses only JSON values at runtime", () => { + for (const value of values) + expect(jsonValueSchema.safeParse(value).success).toBe(true); + for (const value of [undefined, Number.NaN, Infinity, () => 0, [undefined]]) + expect(jsonValueSchema.safeParse(value).success).toBe(false); +}); diff --git a/src/domain/jsonValue.ts b/src/domain/jsonValue.ts index 710e62d61..1917eac31 100644 --- a/src/domain/jsonValue.ts +++ b/src/domain/jsonValue.ts @@ -2,6 +2,10 @@ import { z } from "zod"; /** JSON-safe value shared by domain, application, and adapter boundaries. */ export const jsonValueSchema = z.json(); +// Every JSON value satisfies `{}`. Advertise that instead of Zod's +// self-referential `$defs` projection, which MCP clients and model APIs that +// reject recursive JSON Schemas refuse. Runtime parsing still admits only JSON. +jsonValueSchema._zod.toJSONSchema = () => ({}); /** JSON object boundary for caller-visible parameter maps. */ export const jsonObjectSchema = z.record(z.string(), jsonValueSchema); diff --git a/src/domain/localPath.ts b/src/domain/localPath.ts index 43b491cf9..9248de4d7 100644 --- a/src/domain/localPath.ts +++ b/src/domain/localPath.ts @@ -1,5 +1,9 @@ import { isAbsolute, relative, sep } from "node:path"; +/** True when the value is an absolute filesystem path on the host platform. */ +export const isAbsoluteLocalPath = (value: string): boolean => + isAbsolute(value); + /** Test lexical containment on the host platform; callers must resolve symlinks first. */ export const isPathWithinRoot = (root: string, path: string): boolean => { const value = relative(root, path); diff --git a/src/domain/managed/managedNativeVerificationSchemas.ts b/src/domain/managed/managedNativeVerificationSchemas.ts index 03590b389..8e543bdab 100644 --- a/src/domain/managed/managedNativeVerificationSchemas.ts +++ b/src/domain/managed/managedNativeVerificationSchemas.ts @@ -199,7 +199,10 @@ export const managedNativeVerificationResultSchema = z path: ["summary", "native_body_unresolved"], message: "Native body summary must match unresolved implementations", }); - }); + }) + .describe( + "Counts must agree across the result: native accepted plus unsupported equals total; each P/Invoke summary count equals the number of items with that status; and native_body_unresolved equals native_implementations.unresolved. REA checks these cross-field invariants after constructing the result.", + ); export type ManagedNativeVerificationResult = z.infer< typeof managedNativeVerificationResultSchema diff --git a/src/domain/mobileApplicationInvestigation.test.ts b/src/domain/mobileApplicationInvestigation.test.ts deleted file mode 100644 index d0610263e..000000000 --- a/src/domain/mobileApplicationInvestigation.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - classifyMobileResource, - detectMobileFormat, - detectPlatform, - getAllArchitectures, - hasInternetPermission, - mobileArtifactManifestSchema, - nativeLibsByArchitecture, - type MobileArtifactManifest, - type AndroidManifest, - type SigningInfo, - type NativeLibrary, - type MobileResource, -} from "./mobileApplicationInvestigation.js"; - -describe("mobile application investigation", () => { - it("detects mobile format from extension", () => { - expect(detectMobileFormat("app.apk")).toBe("apk"); - expect(detectMobileFormat("app.aab")).toBe("aab"); - expect(detectMobileFormat("app.ipa")).toBe("ipa"); - expect(detectMobileFormat("classes.dex")).toBe("dex"); - expect(detectMobileFormat("lib.so")).toBe("native_lib"); - expect(detectMobileFormat("readme.txt")).toBeNull(); - }); - - it("detects platform from format", () => { - expect(detectPlatform("apk")).toBe("android"); - expect(detectPlatform("aab")).toBe("android"); - expect(detectPlatform("dex")).toBe("android"); - expect(detectPlatform("ipa")).toBe("ios"); - expect(detectPlatform("native_lib")).toBe("cross_platform"); - }); - - it("classifies resources by path", () => { - expect(classifyMobileResource("res/layout/main.xml")).toBe("layout"); - expect(classifyMobileResource("res/drawable/icon.png")).toBe("drawable"); - expect(classifyMobileResource("assets/data.json")).toBe("asset"); - expect(classifyMobileResource("AndroidManifest.xml")).toBe("manifest"); - expect(classifyMobileResource("app.entitlements")).toBe("entitlements"); - expect(classifyMobileResource("lib/arm64-v8a/libnative.so")).toBe( - "native_lib", - ); - expect(classifyMobileResource("unknown.xyz")).toBe("other"); - }); - - const androidManifest: AndroidManifest = { - package_name: "com.example.app", - version_name: "1.0.0", - version_code: 1, - min_sdk: 21, - target_sdk: 34, - permissions: ["android.permission.INTERNET", "android.permission.CAMERA"], - activities: ["MainActivity"], - services: ["BackgroundService"], - receivers: [], - providers: [], - }; - - const signing: SigningInfo = { - is_signed: true, - signer_identity: "CN=TestSigner", - certificate_fingerprint: "abc123", - signature_scheme: "v2", - is_debuggable: false, - }; - - const nativeLibs: NativeLibrary[] = [ - { - path: "lib/arm64-v8a/libnative.so", - architecture: "arm64-v8a", - size: 100000, - digest: "a".repeat(64), - }, - { - path: "lib/armeabi-v7a/libnative.so", - architecture: "armeabi-v7a", - size: 90000, - digest: "b".repeat(64), - }, - ]; - - const resources: MobileResource[] = [ - { path: "res/layout/main.xml", type: "layout", size: 1000 }, - { path: "res/drawable/icon.png", type: "drawable", size: 5000 }, - ]; - - const validManifest: MobileArtifactManifest = { - format: "apk", - platform: "android", - app_name: "TestApp", - android_manifest: androidManifest, - ios_manifest: null, - signing, - resources, - native_libraries: nativeLibs, - total_size: 5000000, - architectures: ["arm64-v8a", "armeabi-v7a"], - }; - - it("rejects a manifest with a negative total size", () => { - const result = mobileArtifactManifestSchema.safeParse({ - ...validManifest, - total_size: -1, - }); - expect(result.success).toBe(false); - }); - - it("filters native libs by architecture", () => { - const arm64 = nativeLibsByArchitecture(validManifest, "arm64-v8a"); - expect(arm64).toHaveLength(1); - expect(arm64[0]!.architecture).toBe("arm64-v8a"); - }); - - it("checks internet permission", () => { - expect(hasInternetPermission(validManifest)).toBe(true); - const noInternet: MobileArtifactManifest = { - ...validManifest, - android_manifest: { ...androidManifest, permissions: [] }, - }; - expect(hasInternetPermission(noInternet)).toBe(false); - }); - - it("gets all architectures", () => { - const archs = getAllArchitectures(validManifest); - expect(archs).toHaveLength(2); - expect(archs).toContain("arm64-v8a"); - expect(archs).toContain("armeabi-v7a"); - }); -}); diff --git a/src/domain/mobileApplicationInvestigation.ts b/src/domain/mobileApplicationInvestigation.ts deleted file mode 100644 index 95a4b531f..000000000 --- a/src/domain/mobileApplicationInvestigation.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { z } from "zod"; - -/** Supported mobile artifact formats. */ -export const mobileArtifactFormatSchema = z.enum([ - "apk", - "aab", - "ipa", - "dex", - "native_lib", -]); -export type MobileArtifactFormat = z.infer; - -/** Mobile platform. */ -export const mobilePlatformSchema = z.enum([ - "android", - "ios", - "cross_platform", -]); -export type MobilePlatform = z.infer; - -/** Android manifest entry. */ -export const androidManifestSchema = z.strictObject({ - package_name: z.string().min(1), - version_name: z.string().nullable(), - version_code: z.number().int().nullable(), - min_sdk: z.number().int().nullable(), - target_sdk: z.number().int().nullable(), - permissions: z.array(z.string()).default([]), - activities: z.array(z.string()).default([]), - services: z.array(z.string()).default([]), - receivers: z.array(z.string()).default([]), - providers: z.array(z.string()).default([]), -}); -export type AndroidManifest = z.infer; - -/** iOS app manifest (Info.plist). */ -export const iosManifestSchema = z.strictObject({ - bundle_identifier: z.string().min(1), - bundle_version: z.string().nullable(), - short_version: z.string().nullable(), - minimum_os_version: z.string().nullable(), - supported_platforms: z.array(z.string()).default([]), - ui_required_device_capabilities: z.array(z.string()).default([]), - app_permissions: z.array(z.string()).default([]), -}); -/** Signing information. */ -export const signingInfoSchema = z.strictObject({ - is_signed: z.boolean().default(false), - signer_identity: z.string().nullable(), - certificate_fingerprint: z.string().nullable(), - signature_scheme: z.string().nullable(), - is_debuggable: z.boolean().default(false), -}); -export type SigningInfo = z.infer; - -/** Native library entry. */ -export const nativeLibrarySchema = z.strictObject({ - path: z.string().min(1), - architecture: z.string().nullable(), - size: z.number().int().nonnegative(), - digest: z - .string() - .regex(/^[a-f0-9]{64}$/u) - .nullable(), -}); -export type NativeLibrary = z.infer; - -/** Resource entry within a mobile artifact. */ -export const mobileResourceSchema = z.strictObject({ - path: z.string().min(1), - type: z.enum([ - "layout", - "drawable", - "string", - "asset", - "raw", - "native_lib", - "manifest", - "entitlements", - "provisioning", - "other", - ]), - size: z.number().int().nonnegative(), -}); -export type MobileResource = z.infer; - -/** Static analysis result for a mobile artifact. */ -export const mobileArtifactManifestSchema = z.strictObject({ - format: mobileArtifactFormatSchema, - platform: mobilePlatformSchema, - app_name: z.string().nullable(), - android_manifest: androidManifestSchema.nullable(), - ios_manifest: iosManifestSchema.nullable(), - signing: signingInfoSchema, - resources: z.array(mobileResourceSchema).default([]), - native_libraries: z.array(nativeLibrarySchema).default([]), - total_size: z.number().int().nonnegative(), - architectures: z.array(z.string()).default([]), -}); -export type MobileArtifactManifest = z.infer< - typeof mobileArtifactManifestSchema ->; - -/** Detect mobile artifact format from file extension. */ -export function detectMobileFormat(path: string): MobileArtifactFormat | null { - const lower = path.toLowerCase(); - if (lower.endsWith(".apk")) return "apk"; - if (lower.endsWith(".aab")) return "aab"; - if (lower.endsWith(".ipa")) return "ipa"; - if (lower.endsWith(".dex")) return "dex"; - if (lower.endsWith(".so") || lower.endsWith(".dylib") || lower.endsWith(".a")) - return "native_lib"; - return null; -} - -/** Detect platform from format. */ -export function detectPlatform(format: MobileArtifactFormat): MobilePlatform { - switch (format) { - case "apk": - case "aab": - case "dex": - return "android"; - case "ipa": - return "ios"; - case "native_lib": - return "cross_platform"; - } -} - -/** Classify a resource by its path. */ -export function classifyMobileResource(path: string): MobileResource["type"] { - const lower = path.toLowerCase(); - if (lower.includes("androidmanifest.xml")) return "manifest"; - if (lower.includes(".entitlements")) return "entitlements"; - if (lower.includes("provisioning") || lower.includes(".mobileprovision")) - return "provisioning"; - if (lower.includes("/layout/")) return "layout"; - if (lower.includes("/drawable/") || lower.includes("/mipmap/")) - return "drawable"; - if (lower.includes("/values/")) return "string"; - if (lower.includes("assets/") || lower.includes("raw/")) return "asset"; - if (lower.endsWith(".so") || lower.endsWith(".dylib")) return "native_lib"; - if (lower.endsWith(".xml")) return "layout"; - return "other"; -} - -/** Get all native libraries for a specific architecture. */ -export function nativeLibsByArchitecture( - manifest: MobileArtifactManifest, - arch: string, -): NativeLibrary[] { - return manifest.native_libraries.filter((l) => l.architecture === arch); -} - -/** Check if an Android artifact has internet permission. */ -export function hasInternetPermission( - manifest: MobileArtifactManifest, -): boolean { - if (!manifest.android_manifest) return false; - return manifest.android_manifest.permissions.some( - (p) => p.includes("INTERNET") || p.includes("android.permission.INTERNET"), - ); -} - -/** Get all unique architectures across native libraries. */ -export function getAllArchitectures( - manifest: MobileArtifactManifest, -): string[] { - const archs = new Set(); - for (const lib of manifest.native_libraries) { - if (lib.architecture) archs.add(lib.architecture); - } - for (const arch of manifest.architectures) { - archs.add(arch); - } - return [...archs]; -} diff --git a/src/domain/native/nativeUiObservation.ts b/src/domain/native/nativeUiObservation.ts index 9c56488f4..a9378ad7b 100644 --- a/src/domain/native/nativeUiObservation.ts +++ b/src/domain/native/nativeUiObservation.ts @@ -60,10 +60,26 @@ export const nativeUiScenarioInputSchema = z.strictObject({ const nativeUiScreenshotSchema = z .strictObject({ mime_type: z.literal("image/png"), - base64: z.string().min(4), - sha256: z.string().regex(/^[a-f0-9]{64}$/u), - width: z.number().int().positive(), - height: z.number().int().positive(), + base64: z + .string() + .min(4) + .describe( + "Canonical base64 encoding of PNG bytes; the decoded bytes must have a PNG signature and IHDR chunk.", + ), + sha256: z + .string() + .regex(/^[a-f0-9]{64}$/u) + .describe("SHA-256 digest of the decoded PNG bytes."), + width: z + .number() + .int() + .positive() + .describe("Must equal the width encoded in the PNG IHDR chunk."), + height: z + .number() + .int() + .positive() + .describe("Must equal the height encoded in the PNG IHDR chunk."), }) .superRefine((screenshot, context) => { const bytes = decodeCanonicalBase64(screenshot.base64); @@ -117,7 +133,7 @@ export const nativeUiSnapshotSchema = z.strictObject({ title: z.string().nullable(), value: z.string().nullable(), actions: z.array(z.string()), - children_count: z.number().int().nonnegative(), + children_count: z.number().int().nonnegative().nullable(), }), ), truncated: z.boolean(), diff --git a/src/domain/native/objcSwiftMetadata.test.ts b/src/domain/native/objcSwiftMetadata.test.ts index 57223f4a4..e2fa3b765 100644 --- a/src/domain/native/objcSwiftMetadata.test.ts +++ b/src/domain/native/objcSwiftMetadata.test.ts @@ -1,150 +1,8 @@ -import { fc } from "@fast-check/vitest"; import { describe, expect, it } from "vitest"; -import { - countMethodsByType, - isDbSaveComplete, - isGetterSelector, - isSetterSelector, - inspectNativeDispatchMetadata, - propertyNameFromSelector, - swiftDeclsByKind, - type ObjcMethod, - type SwiftDecl, - type DbSaveResult, -} from "./objcSwiftMetadata.js"; +import { inspectNativeDispatchMetadata } from "./objcSwiftMetadata.js"; describe("ObjC/Swift metadata", () => { - it("identifies getter selectors", () => { - expect(isGetterSelector("name")).toBe(true); - expect(isGetterSelector("setName:")).toBe(false); - expect(isGetterSelector("set")).toBe(true); - }); - - it("identifies setter selectors", () => { - expect(isSetterSelector("setName:")).toBe(true); - expect(isSetterSelector("name")).toBe(false); - }); - - it("extracts property name from selector", () => { - expect(propertyNameFromSelector("name")).toBe("name"); - expect(propertyNameFromSelector("setName:")).toBe("name"); - expect(propertyNameFromSelector("set")).toBe("set"); - }); - - it("counts methods by type", () => { - const methods: ObjcMethod[] = [ - { - selector: "init", - method_type: "instance", - address: 0x1000, - is_required: false, - is_optional: false, - }, - { - selector: "alloc", - method_type: "class", - address: 0x2000, - is_required: false, - is_optional: false, - }, - ]; - const counts = countMethodsByType(methods); - expect(counts.instance).toBe(1); - expect(counts.class).toBe(1); - expect(counts.ivar_getter).toBe(0); - expect(counts.ivar_setter).toBe(0); - }); - - it("filters Swift declarations by kind", () => { - const decls: SwiftDecl[] = [ - { - kind: "class", - name: "Foo", - module: null, - access_level: "public", - super_class: null, - protocols: [], - is_final: false, - is_required: false, - is_convenience_init: false, - is_override: false, - }, - ]; - const classes = swiftDeclsByKind(decls, "class"); - expect(classes).toHaveLength(1); - expect(classes[0]!.name).toBe("Foo"); - }); - - it("checks database save completeness", () => { - const result: DbSaveResult = { - operation: "save", - succeeded: true, - preserved_names: 10, - preserved_comments: 5, - preserved_bookmarks: 3, - database_path: "/tmp/db.hop", - error: null, - }; - expect(isDbSaveComplete(result, 10, 5, 3)).toBe(true); - expect(isDbSaveComplete(result, 11, 5, 3)).toBe(false); - }); - - it("is monotone in every expected count and total at the observed count", () => { - fc.assert( - fc.property( - fc.record({ - operation: fc.constantFrom( - "save", - "readback", - "open_database", - "close_database", - "export", - "import", - ), - succeeded: fc.boolean(), - preserved_names: fc.nat({ max: 50 }), - preserved_comments: fc.nat({ max: 50 }), - preserved_bookmarks: fc.nat({ max: 50 }), - database_path: fc.option(fc.string(), { nil: null }), - error: fc.option(fc.string(), { nil: null }), - }), - fc.nat({ max: 50 }), - fc.nat({ max: 50 }), - fc.nat({ max: 50 }), - (result, names, comments, bookmarks) => { - // Completeness is downward closed: if demanding more was met, - // demanding less must also be met. - if (isDbSaveComplete(result, names + 1, comments, bookmarks)) { - expect(isDbSaveComplete(result, names, comments, bookmarks)).toBe( - true, - ); - } - if (isDbSaveComplete(result, names, comments + 1, bookmarks)) { - expect(isDbSaveComplete(result, names, comments, bookmarks)).toBe( - true, - ); - } - if (isDbSaveComplete(result, names, comments, bookmarks + 1)) { - expect(isDbSaveComplete(result, names, comments, bookmarks)).toBe( - true, - ); - } - // Expecting exactly what was preserved is complete iff the save worked. - expect( - isDbSaveComplete( - result, - result.preserved_names, - result.preserved_comments, - result.preserved_bookmarks, - ), - ).toBe(result.succeeded); - }, - ), - { numRuns: 200 }, - ); - }); - it("bounds symbol projections and labels symbol-only metadata as partial", () => { const result = inspectNativeDispatchMetadata( [ diff --git a/src/domain/native/objcSwiftMetadata.ts b/src/domain/native/objcSwiftMetadata.ts index 17079d9fd..5a0f0e052 100644 --- a/src/domain/native/objcSwiftMetadata.ts +++ b/src/domain/native/objcSwiftMetadata.ts @@ -7,7 +7,6 @@ export const objcMethodTypeSchema = z.enum([ "ivar_getter", "ivar_setter", ]); -export type ObjcMethodType = z.infer; /** Objective-C property attribute. */ export const objcPropertyAttributeSchema = z.strictObject({ @@ -27,7 +26,7 @@ export const objcMethodSchema = z.strictObject({ is_required: z.boolean().default(false), is_optional: z.boolean().default(false), }); -export type ObjcMethod = z.infer; +type ObjcMethod = z.infer; /** Objective-C property metadata. */ export const objcPropertySchema = z.strictObject({ @@ -87,7 +86,7 @@ export const objcClassSchema = z.strictObject({ ) .optional(), }); -export type ObjcClass = z.infer; +type ObjcClass = z.infer; /** Exact location of a decoded native metadata value when the provider knows it. */ export const nativeMetadataLocationSchema = z.strictObject({ @@ -162,7 +161,7 @@ export const objcDispatchImplementationSchema = z.strictObject({ decode: nativeMetadataDecodeSchema, evidence: z.array(nativeMetadataEvidenceSchema).min(1), }); -export type ObjcDispatchImplementation = z.infer< +type ObjcDispatchImplementation = z.infer< typeof objcDispatchImplementationSchema >; @@ -224,7 +223,7 @@ export const swiftSymbolSchema = z.strictObject({ decode: nativeMetadataDecodeSchema, evidence: z.array(nativeMetadataEvidenceSchema).min(1), }); -export type SwiftSymbol = z.infer; +type SwiftSymbol = z.infer; /** Per-facet coverage; empty arrays are meaningful only with this record. */ export const nativeMetadataCoverageSchema = z.strictObject({ @@ -246,7 +245,6 @@ export const swiftDeclKindSchema = z.enum([ "global_var", "global_func", ]); -export type SwiftDeclKind = z.infer; /** Swift access level. */ export const swiftAccessLevelSchema = z.enum([ @@ -269,7 +267,6 @@ export const swiftDeclSchema = z.strictObject({ is_convenience_init: z.boolean().default(false), is_override: z.boolean().default(false), }); -export type SwiftDecl = z.infer; /** Database save/read operation type. */ export const dbOperationSchema = z.enum([ @@ -290,7 +287,6 @@ export const dbSaveResultSchema = z.strictObject({ database_path: z.string().nullable(), error: z.string().nullable(), }); -export type DbSaveResult = z.infer; /** Result of deeper ObjC/Swift metadata extraction. */ export const objcSwiftMetadataSchema = z.strictObject({ @@ -479,60 +475,3 @@ const parseMetadataAddress = (address: string | null): number | null => { const value = Number.parseInt(match[1], 16); return Number.isSafeInteger(value) ? value : null; }; - -/** Check if a selector name looks like a getter. */ -export function isGetterSelector(selector: string): boolean { - return /^[a-z][a-zA-Z0-9_]*$/u.test(selector) && !selector.includes(":"); -} - -/** Check if a selector name looks like a setter. */ -export function isSetterSelector(selector: string): boolean { - return selector.startsWith("set") && selector.endsWith(":"); -} - -/** Extract property name from a getter/setter selector. */ -export function propertyNameFromSelector(selector: string): string | null { - if (isSetterSelector(selector)) { - const inner = selector.slice(3, -1); - return inner.charAt(0).toLowerCase() + inner.slice(1); - } - if (isGetterSelector(selector)) { - return selector; - } - return null; -} - -/** Count ObjC methods by type. */ -export function countMethodsByType( - methods: readonly ObjcMethod[], -): Record { - return { - instance: methods.filter((m) => m.method_type === "instance").length, - class: methods.filter((m) => m.method_type === "class").length, - ivar_getter: methods.filter((m) => m.method_type === "ivar_getter").length, - ivar_setter: methods.filter((m) => m.method_type === "ivar_setter").length, - }; -} - -/** Get all Swift declarations of a specific kind. */ -export function swiftDeclsByKind( - decls: readonly SwiftDecl[], - kind: SwiftDeclKind, -): SwiftDecl[] { - return decls.filter((d) => d.kind === kind); -} - -/** Check if a database save preserved all expected items. */ -export function isDbSaveComplete( - result: DbSaveResult, - expectedNames: number, - expectedComments: number, - expectedBookmarks: number, -): boolean { - return ( - result.succeeded && - result.preserved_names >= expectedNames && - result.preserved_comments >= expectedComments && - result.preserved_bookmarks >= expectedBookmarks - ); -} diff --git a/src/domain/packageAnalysis.test.ts b/src/domain/packageAnalysis.test.ts deleted file mode 100644 index b182feb53..000000000 --- a/src/domain/packageAnalysis.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - classifyResource, - detectPackageFormat, - hasValidSignature, - isConfigurationResource, - isManifestResource, - resourcesByType, - totalResourceSize, - type PackageManifest, -} from "./packageAnalysis.js"; - -describe("package analysis", () => { - it("detects package format from extension", () => { - expect(detectPackageFormat("app.msix")).toBe("msix"); - expect(detectPackageFormat("installer.msi")).toBe("msi"); - expect(detectPackageFormat("app.appx")).toBe("appx"); - expect(detectPackageFormat("app.zip")).toBeNull(); - }); - - it("identifies manifest resources", () => { - expect(isManifestResource("AppXManifest.xml")).toBe(true); - expect(isManifestResource("resources.pri")).toBe(false); - }); - - it("identifies configuration resources", () => { - expect(isConfigurationResource("settings.xml")).toBe(true); - expect(isConfigurationResource("config.json")).toBe(true); - expect(isConfigurationResource("app.exe")).toBe(false); - }); - - it("classifies resources by path", () => { - expect(classifyResource("AppXManifest.xml")).toBe("manifest"); - expect(classifyResource("icon.png")).toBe("icon"); - expect(classifyResource("settings.xml")).toBe("configuration"); - expect(classifyResource("cert.cer")).toBe("certificate"); - expect(classifyResource("app.exe")).toBe("executable"); - expect(classifyResource("lib.dll")).toBe("library"); - expect(classifyResource("data.bin")).toBe("other"); - }); - - const validManifest: PackageManifest = { - format: "msix", - name: "TestApp", - publisher: "CN=TestPublisher", - version: "1.0.0.0", - architecture: "x64", - resources: [ - { - path: "AppXManifest.xml", - type: "manifest", - size: 1000, - digest: "a".repeat(64), - }, - { path: "icon.png", type: "icon", size: 5000, digest: "b".repeat(64) }, - { - path: "app.exe", - type: "executable", - size: 50000, - digest: "c".repeat(64), - }, - ], - signature: { - is_signed: true, - signer_subject: "CN=TestPublisher", - certificate_thumbprint: "abc123", - is_valid: true, - is_timestamped: true, - timestamp_signer: "CN=Timestamp", - }, - is_framework: false, - dependencies: ["Microsoft.VCLibs.14.00"], - capabilities: ["internetClient", "localNetwork"], - }; - - it("filters resources by type", () => { - const icons = resourcesByType(validManifest, "icon"); - expect(icons).toHaveLength(1); - expect(icons[0]!.path).toBe("icon.png"); - }); - - it("computes total resource size", () => { - expect(totalResourceSize(validManifest)).toBe(56000); - }); - - it("checks valid signature", () => { - expect(hasValidSignature(validManifest)).toBe(true); - const unsigned: PackageManifest = { - ...validManifest, - signature: { ...validManifest.signature, is_valid: false }, - }; - expect(hasValidSignature(unsigned)).toBe(false); - }); -}); diff --git a/src/domain/packageAnalysis.ts b/src/domain/packageAnalysis.ts deleted file mode 100644 index 4eed30652..000000000 --- a/src/domain/packageAnalysis.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { z } from "zod"; - -/** Supported Windows package formats. */ -export const packageFormatSchema = z.enum(["msix", "msi", "appx"]); -export type PackageFormat = z.infer; - -/** A resource entry within a package. */ -export const packageResourceSchema = z.strictObject({ - /** Resource path within the package. */ - path: z.string().min(1), - /** Resource type. */ - type: z.enum([ - "manifest", - "icon", - "configuration", - "certificate", - "executable", - "library", - "data", - "other", - ]), - /** Size in bytes. */ - size: z.number().int().nonnegative(), - /** SHA-256 digest. */ - digest: z - .string() - .regex(/^[a-f0-9]{64}$/u) - .nullable(), -}); -export type PackageResource = z.infer; - -/** Digital signature information. */ -export const digitalSignatureSchema = z.strictObject({ - /** Whether the package is signed. */ - is_signed: z.boolean(), - /** Signer subject name. */ - signer_subject: z.string().nullable(), - /** Certificate thumbprint. */ - certificate_thumbprint: z.string().nullable(), - /** Whether the signature is valid. */ - is_valid: z.boolean().default(false), - /** Whether the signature is timestamped. */ - is_timestamped: z.boolean().default(false), - /** Timestamp signer if present. */ - timestamp_signer: z.string().nullable(), -}); - -/** A Windows package manifest for static analysis. */ -export const packageManifestSchema = z.strictObject({ - /** Package format. */ - format: packageFormatSchema, - /** Package identity name. */ - name: z.string().min(1), - /** Package publisher. */ - publisher: z.string().nullable(), - /** Package version. */ - version: z.string().nullable(), - /** Architecture. */ - architecture: z.enum(["x86", "x64", "arm", "arm64", "neutral", "unknown"]), - /** Package resources. */ - resources: z.array(packageResourceSchema).default([]), - /** Digital signature. */ - signature: digitalSignatureSchema, - /** Whether the package is a framework. */ - is_framework: z.boolean().default(false), - /** Package dependencies. */ - dependencies: z.array(z.string()).default([]), - /** Capabilities declared in the manifest. */ - capabilities: z.array(z.string()).default([]), -}); -export type PackageManifest = z.infer; - -/** Detect package format from file extension. */ -export function detectPackageFormat(path: string): PackageFormat | null { - const lower = path.toLowerCase(); - if (lower.endsWith(".msix")) return "msix"; - if (lower.endsWith(".msi")) return "msi"; - if (lower.endsWith(".appx")) return "appx"; - return null; -} - -/** Check if a resource path looks like a manifest. */ -export function isManifestResource(path: string): boolean { - const lower = path.toLowerCase(); - return ( - lower.includes("appxmanifest.xml") || - lower.includes("appxmanifest") || - lower.includes("manifest.xml") || - lower.endsWith("_manifest.xml") - ); -} - -/** Check if a resource path looks like a configuration file. */ -export function isConfigurationResource(path: string): boolean { - const lower = path.toLowerCase(); - return ( - lower.endsWith(".xml") || - lower.endsWith(".json") || - lower.endsWith(".ini") || - lower.endsWith(".config") - ); -} - -/** Classify a resource by its path. */ -export function classifyResource(path: string): PackageResource["type"] { - if (isManifestResource(path)) return "manifest"; - const lower = path.toLowerCase(); - if ( - lower.endsWith(".ico") || - lower.endsWith(".png") || - lower.endsWith(".jpg") || - lower.endsWith(".jpeg") - ) - return "icon"; - if (isConfigurationResource(path)) return "configuration"; - if ( - lower.endsWith(".cer") || - lower.endsWith(".pfx") || - lower.endsWith(".p12") - ) - return "certificate"; - if (lower.endsWith(".exe") || lower.endsWith(".dll")) - return lower.endsWith(".exe") ? "executable" : "library"; - return "other"; -} - -/** Get all resources of a specific type. */ -export function resourcesByType( - manifest: PackageManifest, - type: PackageResource["type"], -): PackageResource[] { - return manifest.resources.filter((r) => r.type === type); -} - -/** Get the total size of all resources. */ -export function totalResourceSize(manifest: PackageManifest): number { - return manifest.resources.reduce((sum, r) => sum + r.size, 0); -} - -/** Check if a package has a valid signature. */ -export function hasValidSignature(manifest: PackageManifest): boolean { - return manifest.signature.is_signed && manifest.signature.is_valid; -} diff --git a/src/domain/peInspection.test.ts b/src/domain/peInspection.test.ts deleted file mode 100644 index c05141487..000000000 --- a/src/domain/peInspection.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - countExecutableSections, - getExportedFunctions, - getImportedDlls, - hasPdbInfo, - parseDebugType, - parseImageCharacteristics, - parseMachineType, - parseSectionCharacteristics, - peManifestSchema, - type PeManifest, -} from "./peInspection.js"; - -const validManifest: PeManifest = { - dos_magic: 0x5a4d, - pe_signature: 0x00004550, - machine: "x64", - number_of_sections: 3, - timestamp: 0x12345678, - is_dll: false, - is_executable: true, - is_console: true, - entry_point: 0x1000, - image_base: 0x400000, - section_alignment: 0x1000, - subsystem_version: "10.0", - sections: [ - { - name: ".text", - virtual_size: 0x1000, - virtual_address: 0x1000, - raw_size: 0x1000, - raw_offset: 0x400, - characteristics: { - has_code: true, - has_initialized_data: false, - has_uninitialized_data: false, - readable: true, - writable: false, - executable: true, - }, - }, - { - name: ".data", - virtual_size: 0x500, - virtual_address: 0x2000, - raw_size: 0x500, - raw_offset: 0x1400, - characteristics: { - has_code: false, - has_initialized_data: true, - has_uninitialized_data: false, - readable: true, - writable: true, - executable: false, - }, - }, - { - name: ".rdata", - virtual_size: 0x200, - virtual_address: 0x3000, - raw_size: 0x200, - raw_offset: 0x1900, - characteristics: { - has_code: false, - has_initialized_data: true, - has_uninitialized_data: false, - readable: true, - writable: false, - executable: false, - }, - }, - ], - imports: [ - { - dll: "KERNEL32.DLL", - function_name: "CreateFileW", - ordinal: null, - is_delay_import: false, - }, - { - dll: "KERNEL32.DLL", - function_name: "WriteFile", - ordinal: null, - is_delay_import: false, - }, - { - dll: "USER32.DLL", - function_name: "MessageBoxW", - ordinal: 100, - is_delay_import: false, - }, - ], - exports: [ - { name: "DllMain", ordinal: 1, rva: 0x1000, forwarder: null }, - { name: "MyFunction", ordinal: 2, rva: 0x1050, forwarder: null }, - ], - resources: [ - { type: "RT_VERSION", name: "1", language: 0x0409, rva: 0x4000, size: 100 }, - ], - debug_entries: [ - { - type: "CODEVIEW", - rva: 0x5000, - size: 100, - pdb_guid: "AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP", - pdb_age: 1, - pdb_path: "C:\\src\\app.pdb", - }, - ], - tls_callbacks: [{ callback_rva: 0x6000, index: 0 }], - relocations_count: 50, - image_characteristics: ["Executable", "Console"], -}; - -describe("PE/COFF static inspection", () => { - it("parses machine types", () => { - expect(parseMachineType(0x014c)).toBe("x86"); - expect(parseMachineType(0x8664)).toBe("x64"); - expect(parseMachineType(0x01c0)).toBe("arm"); - expect(parseMachineType(0xaa64)).toBe("arm64"); - expect(parseMachineType(0x0200)).toBe("ia64"); - expect(parseMachineType(0xffff)).toBe("unknown"); - }); - - it("parses section characteristics", () => { - const CODE = 0x00000020; - const READ = 0x40000000; - const EXECUTE = 0x20000000; - const result = parseSectionCharacteristics(CODE | READ | EXECUTE); - expect(result.has_code).toBe(true); - expect(result.readable).toBe(true); - expect(result.executable).toBe(true); - expect(result.writable).toBe(false); - }); - - it("parses image characteristics", () => { - const DLL = 0x2000; - const EXEC = 0x0002; - const flags = parseImageCharacteristics(DLL | EXEC); - expect(flags).toContain("DLL"); - expect(flags).toContain("Executable"); - }); - - it("parses debug types", () => { - expect(parseDebugType(2)).toBe("CODEVIEW"); - expect(parseDebugType(1)).toBe("COFF"); - expect(parseDebugType(99)).toBe("UNKNOWN"); - }); - - it("rejects a manifest whose section table is malformed", () => { - const malformed = { - ...validManifest, - sections: [{ ...validManifest.sections[0]!, name: 7 }], - }; - const result = peManifestSchema.safeParse(malformed); - expect(result.success).toBe(false); - }); - - it("accepts static manifests with more than 96 PE sections", () => { - const section = validManifest.sections[0]; - if (section === undefined) throw new Error("Expected PE section fixture"); - const sections = Array.from({ length: 97 }, (_, index) => ({ - ...section, - name: `section-${String(index)}`, - })); - - expect( - peManifestSchema.parse({ - ...validManifest, - number_of_sections: sections.length, - sections, - }).sections, - ).toHaveLength(97); - }); - - it("detects PDB info", () => { - expect(hasPdbInfo(validManifest)).toBe(true); - const withoutPdb: PeManifest = { - ...validManifest, - debug_entries: [ - { - type: "COFF", - rva: 0x5000, - size: 100, - pdb_guid: null, - pdb_age: null, - pdb_path: null, - }, - ], - }; - expect(hasPdbInfo(withoutPdb)).toBe(false); - }); - - it("gets imported DLLs", () => { - const dlls = getImportedDlls(validManifest); - expect(dlls).toHaveLength(2); - expect(dlls).toContain("KERNEL32.DLL"); - expect(dlls).toContain("USER32.DLL"); - }); - - it("gets exported functions", () => { - const exports = getExportedFunctions(validManifest); - expect(exports).toHaveLength(2); - expect(exports).toContain("DllMain"); - expect(exports).toContain("MyFunction"); - }); - - it("counts executable sections", () => { - expect(countExecutableSections(validManifest)).toBe(1); - }); -}); diff --git a/src/domain/peInspection.ts b/src/domain/peInspection.ts deleted file mode 100644 index 9e58dea0b..000000000 --- a/src/domain/peInspection.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { z } from "zod"; - -/** PE/COFF section characteristics flags. */ -export const sectionCharacteristicsSchema = z.object({ - /** Section contains executable code. */ - has_code: z.boolean().default(false), - /** Section contains initialized data. */ - has_initialized_data: z.boolean().default(false), - /** Section contains uninitialized data. */ - has_uninitialized_data: z.boolean().default(false), - /** Section can be read. */ - readable: z.boolean().default(false), - /** Section can be written. */ - writable: z.boolean().default(false), - /** Section can be executed. */ - executable: z.boolean().default(false), -}); - -/** PE/COFF section header. */ -export const peSectionSchema = z.strictObject({ - name: z.string().min(1), - virtual_size: z.number().int().nonnegative(), - virtual_address: z.number().int().nonnegative(), - raw_size: z.number().int().nonnegative(), - raw_offset: z.number().int().nonnegative(), - characteristics: sectionCharacteristicsSchema, -}); - -/** PE import entry. */ -export const peImportSchema = z.strictObject({ - dll: z.string().min(1), - function_name: z.string().min(1), - ordinal: z.number().int().nullable(), - is_delay_import: z.boolean().default(false), -}); - -/** PE export entry. */ -export const peExportSchema = z.strictObject({ - name: z.string().min(1), - ordinal: z.number().int().nonnegative(), - rva: z.number().int().nonnegative(), - forwarder: z.string().nullable(), -}); - -/** PE/COFF resource entry. */ -export const peResourceSchema = z.strictObject({ - type: z.string().min(1), - name: z.string().nullable(), - language: z.number().int().nullable(), - rva: z.number().int().nonnegative(), - size: z.number().int().nonnegative(), -}); - -/** PE debug directory entry. */ -export const peDebugEntrySchema = z.strictObject({ - type: z.string().min(1), - rva: z.number().int().nonnegative(), - size: z.number().int().nonnegative(), - /** PDB GUID if this is a CodeView entry. */ - pdb_guid: z.string().nullable(), - /** PDB age if this is a CodeView entry. */ - pdb_age: z.number().int().nullable(), - /** PDB path if this is a CodeView entry. */ - pdb_path: z.string().nullable(), -}); - -/** TLS callback entry. */ -export const peTlsCallbackSchema = z.strictObject({ - callback_rva: z.number().int().nonnegative(), - index: z.number().int().nonnegative(), -}); - -/** Architecture/machine type. */ -export const peMachineTypeSchema = z.enum([ - "x86", - "x64", - "arm", - "arm64", - "ia64", - "unknown", -]); -export type PeMachineType = z.infer; - -/** PE/COFF manifest for static inspection. */ -export const peManifestSchema = z.strictObject({ - /** DOS header magic. */ - dos_magic: z.literal(0x5a4d), - /** PE signature. */ - pe_signature: z.literal(0x00004550), - /** Machine type. */ - machine: peMachineTypeSchema, - /** Number of sections. */ - number_of_sections: z.number().int().positive(), - /** Timestamp from the COFF header. */ - timestamp: z.number().int().nullable(), - /** PE characteristics flags. */ - is_dll: z.boolean().default(false), - is_executable: z.boolean().default(false), - is_console: z.boolean().default(true), - /** Entry point RVA. */ - entry_point: z.number().int().nonnegative(), - /** Image base. */ - image_base: z.number().int().nonnegative(), - /** Section alignment. */ - section_alignment: z.number().int().positive(), - /** Subsystem version. */ - subsystem_version: z.string().nullable(), - /** Sections. */ - sections: z.array(peSectionSchema), - /** Imports. */ - imports: z.array(peImportSchema).default([]), - /** Exports. */ - exports: z.array(peExportSchema).default([]), - /** Resources. */ - resources: z.array(peResourceSchema).default([]), - /** Debug directory entries. */ - debug_entries: z.array(peDebugEntrySchema).default([]), - /** TLS callbacks. */ - tls_callbacks: z.array(peTlsCallbackSchema).default([]), - /** Relocations count (not all entries). */ - relocations_count: z.number().int().nonnegative().default(0), - /** Image file characteristics. */ - image_characteristics: z.array(z.string()).default([]), -}); -export type PeManifest = z.infer; - -/** Parse machine type from COFF machine field. */ -export function parseMachineType(machine: number): PeMachineType { - switch (machine) { - case 0x014c: - return "x86"; - case 0x8664: - return "x64"; - case 0x01c0: - case 0x01c2: - case 0x01c4: - return "arm"; - case 0xaa64: - return "arm64"; - case 0x0200: - return "ia64"; - default: - return "unknown"; - } -} - -/** Parse section characteristics flags. */ -export function parseSectionCharacteristics( - characteristics: number, -): z.infer { - const IMAGE_SCN_CNT_CODE = 0x00000020; - const IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040; - const IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080; - const IMAGE_SCN_MEM_READ = 0x40000000; - const IMAGE_SCN_MEM_WRITE = 0x80000000; - const IMAGE_SCN_MEM_EXECUTE = 0x20000000; - - return { - has_code: (characteristics & IMAGE_SCN_CNT_CODE) !== 0, - has_initialized_data: - (characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) !== 0, - has_uninitialized_data: - (characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) !== 0, - readable: (characteristics & IMAGE_SCN_MEM_READ) !== 0, - writable: (characteristics & IMAGE_SCN_MEM_WRITE) !== 0, - executable: (characteristics & IMAGE_SCN_MEM_EXECUTE) !== 0, - }; -} - -/** Parse image characteristics flags. */ -export function parseImageCharacteristics(characteristics: number): string[] { - const IMAGE_DLL = 0x2000; - const IMAGE_EXECUTABLE = 0x0002; - const IMAGE_CONSOLE = 0x0003; - const flags: string[] = []; - if (characteristics & IMAGE_DLL) flags.push("DLL"); - if (characteristics & IMAGE_EXECUTABLE) flags.push("Executable"); - if ((characteristics & 0x0000000f) === IMAGE_CONSOLE) flags.push("Console"); - return flags; -} - -/** Parse a PE debug directory type. */ -export function parseDebugType(type: number): string { - switch (type) { - case 0: - return "UNKNOWN"; - case 1: - return "COFF"; - case 2: - return "CODEVIEW"; - case 3: - return "POGO"; - case 4: - return "MISC"; - default: - return "UNKNOWN"; - } -} - -/** Check if a PE manifest has PDB debug info. */ -export function hasPdbInfo(manifest: PeManifest): boolean { - return manifest.debug_entries.some( - (e) => e.pdb_guid !== null && e.pdb_path !== null, - ); -} - -/** Get all imported DLLs from a PE manifest. */ -export function getImportedDlls(manifest: PeManifest): string[] { - return [...new Set(manifest.imports.map((i) => i.dll))]; -} - -/** Get all exported function names from a PE manifest. */ -export function getExportedFunctions(manifest: PeManifest): string[] { - return manifest.exports.map((e) => e.name); -} - -/** Count executable sections. */ -export function countExecutableSections(manifest: PeManifest): number { - return manifest.sections.filter((s) => s.characteristics.executable).length; -} diff --git a/src/domain/process/processCapture.comparison.test.ts b/src/domain/process/processCapture.comparison.test.ts new file mode 100644 index 000000000..93d724627 --- /dev/null +++ b/src/domain/process/processCapture.comparison.test.ts @@ -0,0 +1,160 @@ +import { expect, it } from "vitest"; + +import { + compareUnverifiedProcessCaptures as compareProcessCaptures, + emptyProcessCapture as emptyCapture, +} from "./processCapture.fixture.js"; + +it("distinguishes added evidence from unknown observations", () => { + const capture = emptyCapture(); + const added = compareProcessCaptures(capture, { + ...capture, + frames: [{ sequence: 0, at_ms: 0, data: "new" }], + rendered_frames: [ + { + sequence: 0, + at_ms: 0, + columns: 3, + rows: 1, + cursor_x: 3, + cursor_y: 0, + active_buffer: "normal", + lines: ["new"], + serialized_state: "new", + }, + ], + }); + expect(added).toMatchObject({ terminal: "added", status: "changed" }); + + for (const [scope, dimension] of [ + ["process", "process"], + ["interaction", "interaction"], + ] as const) { + expect( + compareProcessCaptures( + { + ...capture, + residual_unknowns: [{ scope, reason: "observation was partial" }], + }, + capture, + ), + ).toMatchObject({ + status: "unknown", + [dimension]: "unknown", + terminal: "unchanged", + }); + } +}); + +it("compares raw terminal and process observations while honoring normalization shape", () => { + const capture = emptyCapture(); + expect( + compareProcessCaptures(capture, { + ...capture, + normalization: { + patterns: [], + time_bucket_ms: 10, + ports: true, + pids: true, + paths: true, + }, + }), + ).toMatchObject({ + status: "unchanged", + first_divergence: { status: "none" }, + }); + + const terminal = compareProcessCaptures( + { ...capture, frames: [{ sequence: 0, at_ms: 0, data: "bar" }] }, + { ...capture, frames: [{ sequence: 0, at_ms: 0, data: "foo\rbar" }] }, + ); + expect(terminal).toMatchObject({ + terminal: "changed", + first_divergence: { status: "found", dimension: "terminal" }, + }); + + expect( + compareProcessCaptures( + { + ...capture, + process_samples: [ + { + at_ms: 10, + pid: 1, + parent_pid: 0, + process_group_id: 1, + session_id: 1, + command: "worker", + }, + ], + }, + { + ...capture, + process_samples: [ + { + at_ms: 20, + pid: 1, + parent_pid: 2, + process_group_id: 1, + session_id: 1, + command: "worker", + }, + ], + }, + ), + ).toMatchObject({ process: "changed", status: "changed" }); +}); + +it("keeps filesystem comparison unknown without coverage and reports observed changes", () => { + const capture = emptyCapture(); + const incomplete = { + ...capture, + residual_unknowns: [ + { scope: "filesystem" as const, reason: "watcher unavailable" }, + ], + }; + expect(compareProcessCaptures(incomplete, incomplete)).toMatchObject({ + filesystem: "unknown", + status: "unknown", + }); + + const finalFile = { + path: "root_0:created.txt", + type: "file" as const, + mode: 0o644, + size: 4, + sha256: "a".repeat(64), + symlink_target: null, + }; + expect( + compareProcessCaptures(capture, { + ...capture, + filesystem_checkpoints: [ + { name: "before", at_ms: 0, files: [], effects: [], truncated: false }, + { + name: "after_settlement", + at_ms: 10, + files: [finalFile], + effects: [ + { + path: finalFile.path, + status: "created", + before: null, + after: finalFile, + }, + ], + truncated: false, + }, + ], + files_after: [finalFile], + filesystem_effects: [ + { + path: finalFile.path, + status: "created", + before: null, + after: finalFile, + }, + ], + }), + ).toMatchObject({ filesystem: "changed", status: "changed" }); +}); diff --git a/src/domain/processCapture.fixture.ts b/src/domain/process/processCapture.fixture.ts similarity index 100% rename from src/domain/processCapture.fixture.ts rename to src/domain/process/processCapture.fixture.ts diff --git a/src/domain/process/processCapture.ts b/src/domain/process/processCapture.ts new file mode 100644 index 000000000..7c41ccb16 --- /dev/null +++ b/src/domain/process/processCapture.ts @@ -0,0 +1,661 @@ +import { z } from "zod"; +import { jsonValueSchema } from "../jsonValue.js"; + +import { normalizationSchema } from "./processScenario.js"; +import { collectProcessCaptureIssues } from "./processCaptureValidation.js"; + +export * from "./processScenario.js"; + +/** Normalized raw PTY chunk, preserving transport-level output differences. */ +export interface TerminalFrame { + readonly sequence: number; + readonly at_ms: number; + readonly data: string; +} + +/** Serialized terminal state after interpreting control and resize sequences. */ +export interface RenderedTerminalFrame { + readonly sequence: number; + readonly at_ms: number; + readonly columns: number; + readonly rows: number; + readonly cursor_x: number; + readonly cursor_y: number; + readonly active_buffer: "normal" | "alternate"; + readonly lines: readonly string[]; + readonly serialized_state: string; +} + +/** Scheduled terminal interaction with its observed dispatch outcome. */ +export interface InteractionEvent { + readonly sequence: number; + readonly scheduled_at_ms: number; + readonly dispatched_at_ms: number; + readonly type: "input" | "resize" | "signal"; + readonly data: string; + readonly outcome: "dispatched" | "target_exited" | "failed"; +} + +/** One filesystem state used for before/after comparison. */ +interface FileStateIdentity { + readonly path: string; + readonly mode: number; + readonly size: number; +} + +export type FileState = FileStateIdentity & + ( + | { + readonly type: "file"; + readonly sha256: string | null; + readonly symlink_target: null; + } + | { + readonly type: "symlink"; + readonly sha256: null; + readonly symlink_target: string; + } + | { + readonly type: "directory" | "other"; + readonly sha256: null; + readonly symlink_target: null; + } + ); + +interface FileEffectIdentity { + readonly path: string; +} + +type FileEffect = FileEffectIdentity & + ( + | { + readonly status: "created"; + readonly before: null; + readonly after: FileState; + } + | { + readonly status: "deleted"; + readonly before: FileState; + readonly after: null; + } + | { + readonly status: "modified" | "unchanged"; + readonly before: FileState; + readonly after: FileState; + } + ); + +/** A sampled owned-process observation; sampling cannot prove syscall completeness. */ +export interface ProcessSample { + readonly at_ms: number; + readonly pid: number; + readonly parent_pid: number; + readonly command: string; + readonly process_group_id: number | null; + readonly session_id: number | null; +} + +/** Initial or final filesystem snapshot selected for observation. */ +export interface FilesystemCheckpoint { + readonly name: string; + readonly at_ms: number; + readonly files: readonly FileState[]; + readonly effects: readonly FileEffect[]; + readonly truncated: boolean; +} + +/** Capture collection whose members participate in global observation order. */ +export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [ + "frames", + "rendered_frames", + "interaction_events", + "lifecycle", + "process_samples", + "filesystem_checkpoints", +] as const; + +export type ProcessCaptureEventCollection = + (typeof PROCESS_CAPTURE_EVENT_COLLECTIONS)[number]; + +/** One reference from global observation order into a capture collection. */ +export interface ProcessCaptureEventJournalEntry { + readonly capture_order: number; + readonly collection: ProcessCaptureEventCollection; + readonly index: number; +} + +/** Shared observation callback used by every process-capture producer. */ +export type RecordProcessCaptureEvent = ( + collection: ProcessCaptureEventCollection, + index: number, +) => void; + +/** Observed process-tree settlement and the cleanup required by that state. */ +export type ProcessSettlement = + | VerifiedProcessSettlement + | { + readonly state: "quiesced"; + readonly elapsed_ms: number; + readonly cleanup_outcome: "failed"; + }; + +export type VerifiedProcessSettlement = + | { + readonly state: "quiesced"; + readonly elapsed_ms: number; + readonly cleanup_outcome: "not_required"; + } + | { + readonly state: "alive_at_deadline" | "unverifiable"; + readonly elapsed_ms: number; + readonly cleanup_outcome: "cleaned" | "failed"; + }; + +/** + * Process capture observation set. + * + * `truncated` and `residual_unknowns` are semantic evidence: consumers must not + * infer equivalence from matching bounded observations when either is present. + */ +export interface UnverifiedProcessCapture { + readonly manifest: { + readonly rea_version: string; + readonly provider_version: string; + readonly platform: string; + readonly architecture: string; + readonly pty_backend: "node-pty"; + readonly started_at: string; + readonly completed_at: string; + readonly scenario: Readonly>; + readonly comparison_contract: Readonly>; + readonly full_scenario_sha256: string; + readonly comparison_contract_sha256: string; + /** Digest of the selected file sampled before spawn, when readable. */ + readonly selected_executable_sha256: string | null; + /** Digest associated with the launch only when path metadata stayed stable across spawn. */ + readonly executable_sha256: string | null; + /** + * Digest recorded by the original v3 format before selected and launch + * executable digests were distinguished. Kept only on migrated captures. + */ + readonly legacy_executable_sha256?: string | undefined; + readonly executable_identity: { + readonly state: "path_metadata_unchanged" | "unknown"; + readonly reason: string | null; + }; + readonly normalization_sha256: string; + }; + readonly normalization: z.infer; + readonly frames: readonly TerminalFrame[]; + readonly rendered_frames: readonly RenderedTerminalFrame[]; + readonly interaction_events: readonly InteractionEvent[]; + readonly exit: { + readonly code: number | null; + readonly signal: number | null; + readonly reason: "exited" | "timeout" | "idle_timeout"; + }; + readonly settlement: VerifiedProcessSettlement; + readonly process_samples: readonly ProcessSample[]; + readonly filesystem_checkpoints: readonly FilesystemCheckpoint[]; + /** + * Global observation order across independently recorded collections. + * + * Absence is accepted for captures written before this journal existed. + */ + readonly event_journal?: readonly ProcessCaptureEventJournalEntry[]; + readonly files_before: readonly FileState[]; + readonly files_after: readonly FileState[]; + readonly filesystem_effects: readonly FileEffect[]; + readonly truncated: boolean; + readonly limitations: readonly string[]; + readonly residual_unknowns: readonly { + readonly scope: + | "terminal" + | "interaction" + | "exit" + | "process" + | "filesystem" + | "cleanup" + | "network" + | "environment"; + readonly reason: string; + }[]; + readonly cleanup: { + readonly owned_process_group: "verified"; + readonly temporary_root: "removed"; + }; +} + +const fileStateShape = { + path: z.string(), + mode: z.number().int().nonnegative(), + size: z.number().int().nonnegative(), +}; + +/** A resource cleanup result retained when observations cannot be verified. */ +export interface ProcessCaptureResourceCleanup { + readonly state: "cleaned" | "failed" | "unverified" | "not_required"; + readonly reason: string | null; +} + +export interface ProcessCaptureCleanupReport { + readonly owned_process_group: ProcessCaptureResourceCleanup; + readonly terminal_renderer: ProcessCaptureResourceCleanup; + readonly temporary_root: ProcessCaptureResourceCleanup; +} + +/** One observation field that may be unavailable when capture completion fails. */ +export type PartialProcessObservationField = + | { readonly state: "available"; readonly value: T } + | { readonly state: "unavailable"; readonly reason: string }; + +/** Snapshot facts available before filesystem comparison can be completed. */ +/** Snapshot facts available before filesystem comparison can be completed. */ +export interface PartialFilesystemSnapshot { + readonly files: readonly FileState[]; + readonly truncated: boolean; +} + +/** Available and unavailable fields when a run never becomes a full capture. */ +/** Available and unavailable fields when a run never becomes a full capture. */ +export interface IncompleteProcessCaptureObservations { + readonly target_pid: PartialProcessObservationField; + readonly frames: PartialProcessObservationField; + readonly rendered_frames: PartialProcessObservationField< + readonly RenderedTerminalFrame[] + >; + readonly interaction_events: PartialProcessObservationField< + readonly InteractionEvent[] + >; + readonly exit: PartialProcessObservationField<{ + readonly code: number | null; + readonly signal: number | null; + readonly reason: "exited" | "timeout" | "idle_timeout" | "cancelled"; + }>; + readonly settlement: PartialProcessObservationField<{ + readonly state: "quiesced" | "alive_at_deadline" | "unverifiable"; + readonly elapsed_ms: number; + }>; + readonly process_samples: PartialProcessObservationField< + readonly ProcessSample[] + >; + readonly filesystem_snapshots: { + readonly before: PartialProcessObservationField; + readonly after: PartialProcessObservationField; + }; + readonly event_journal: PartialProcessObservationField< + readonly ProcessCaptureEventJournalEntry[] + >; + readonly manifest: PartialProcessObservationField< + UnverifiedProcessCapture["manifest"] + >; +} + +const fileStateSchema = z.discriminatedUnion("type", [ + z.object({ + ...fileStateShape, + type: z.literal("file"), + sha256: z + .string() + .regex(/^[a-f0-9]{64}$/u) + .nullable(), + symlink_target: z.null(), + }), + z.object({ + ...fileStateShape, + type: z.literal("symlink"), + sha256: z.null(), + symlink_target: z.string(), + }), + z.object({ + ...fileStateShape, + type: z.enum(["directory", "other"]), + sha256: z.null(), + symlink_target: z.null(), + }), +]); +const fileEffectSchema = z.discriminatedUnion("status", [ + z.object({ + path: z.string(), + status: z.literal("created"), + before: z.null(), + after: fileStateSchema, + }), + z.object({ + path: z.string(), + status: z.literal("deleted"), + before: fileStateSchema, + after: z.null(), + }), + z.object({ + path: z.string(), + status: z.enum(["modified", "unchanged"]), + before: fileStateSchema, + after: fileStateSchema, + }), +]); +/** Exact serialized shape of a process capture. */ +const processSettlementSchema = z.discriminatedUnion("state", [ + z.object({ + state: z.literal("quiesced"), + elapsed_ms: z.number().int().nonnegative(), + cleanup_outcome: z.enum(["not_required", "failed"]), + }), + z.object({ + state: z.enum(["alive_at_deadline", "unverifiable"]), + elapsed_ms: z.number().int().nonnegative(), + cleanup_outcome: z.enum(["cleaned", "failed"]), + }), +]); + +const processCaptureShapeSchema = z.strictObject({ + manifest: z.strictObject({ + rea_version: z.string().min(1), + provider_version: z.string().min(1), + platform: z.string().min(1), + architecture: z.string().min(1), + pty_backend: z.literal("node-pty"), + started_at: z.iso.datetime(), + completed_at: z.iso.datetime(), + scenario: z.record(z.string(), jsonValueSchema), + comparison_contract: z.record(z.string(), jsonValueSchema), + full_scenario_sha256: z.string().regex(/^[a-f0-9]{64}$/u), + comparison_contract_sha256: z.string().regex(/^[a-f0-9]{64}$/u), + selected_executable_sha256: z + .string() + .regex(/^[a-f0-9]{64}$/u) + .nullable(), + executable_sha256: z + .string() + .regex(/^[a-f0-9]{64}$/u) + .nullable(), + legacy_executable_sha256: z + .string() + .regex(/^[a-f0-9]{64}$/u) + .optional(), + executable_identity: z.strictObject({ + state: z.enum(["path_metadata_unchanged", "unknown"]), + reason: z.string().nullable(), + }), + normalization_sha256: z.string().regex(/^[a-f0-9]{64}$/u), + }), + normalization: normalizationSchema, + frames: z.array( + z.object({ + sequence: z.number().int().nonnegative(), + at_ms: z.number().int().nonnegative(), + data: z.string(), + }), + ), + rendered_frames: z.array( + z.object({ + sequence: z.number().int().nonnegative(), + at_ms: z.number().int().nonnegative(), + columns: z.number().int().positive(), + rows: z.number().int().positive(), + cursor_x: z.number().int().nonnegative(), + cursor_y: z.number().int().nonnegative(), + active_buffer: z.enum(["normal", "alternate"]), + lines: z.array(z.string()), + serialized_state: z.string(), + }), + ), + interaction_events: z.array( + z.object({ + sequence: z.number().int().nonnegative(), + scheduled_at_ms: z.number().int().nonnegative(), + dispatched_at_ms: z.number().int().nonnegative(), + type: z.enum(["input", "resize", "signal"]), + data: z.string(), + outcome: z.enum(["dispatched", "target_exited", "failed"]), + }), + ), + exit: z.object({ + code: z.number().int().nullable(), + signal: z.number().int().nullable(), + reason: z.enum(["exited", "timeout", "idle_timeout"]), + }), + settlement: z.discriminatedUnion("state", [ + z.object({ + state: z.literal("quiesced"), + elapsed_ms: z.number().int().nonnegative(), + cleanup_outcome: z.literal("not_required"), + }), + z.object({ + state: z.enum(["alive_at_deadline", "unverifiable"]), + elapsed_ms: z.number().int().nonnegative(), + cleanup_outcome: z.enum(["cleaned", "failed"]), + }), + ]), + process_samples: z.array( + z.object({ + at_ms: z.number().int().nonnegative(), + pid: z.number().int().positive(), + parent_pid: z.number().int().nonnegative(), + command: z.string(), + process_group_id: z.number().int().positive().nullable(), + session_id: z.number().int().nonnegative().nullable(), + }), + ), + filesystem_checkpoints: z.array( + z.object({ + name: z.enum(["before", "after_settlement"]), + at_ms: z.number().int().nonnegative(), + files: z.array(fileStateSchema), + effects: z.array(fileEffectSchema), + truncated: z.boolean(), + }), + ), + event_journal: z + .array( + z.object({ + capture_order: z.number().int().nonnegative(), + collection: z.enum(PROCESS_CAPTURE_EVENT_COLLECTIONS), + index: z.number().int().nonnegative(), + }), + ) + .default([]), + files_before: z.array(fileStateSchema), + files_after: z.array(fileStateSchema), + filesystem_effects: z.array(fileEffectSchema), + truncated: z.boolean(), + limitations: z.array(z.string()), + residual_unknowns: z.array( + z.object({ + scope: z.enum([ + "terminal", + "interaction", + "exit", + "process", + "filesystem", + "cleanup", + "network", + "environment", + ]), + reason: z.string(), + }), + ), + cleanup: z.object({ + owned_process_group: z.literal("verified"), + temporary_root: z.literal("removed"), + }), +}); + +const processCleanupResourceSchema = z.strictObject({ + state: z.enum(["cleaned", "failed", "unverified", "not_required"]), + reason: z.string().nullable(), +}); + +const partialObservationFieldSchema = ( + value: Schema, +) => + z.discriminatedUnion("state", [ + z.strictObject({ state: z.literal("available"), value }), + z.strictObject({ state: z.literal("unavailable"), reason: z.string() }), + ]); + +const incompleteProcessCaptureObservationsSchema = z.strictObject({ + target_pid: partialObservationFieldSchema(z.number().int().positive()), + frames: partialObservationFieldSchema(processCaptureShapeSchema.shape.frames), + rendered_frames: partialObservationFieldSchema( + processCaptureShapeSchema.shape.rendered_frames, + ), + interaction_events: partialObservationFieldSchema( + processCaptureShapeSchema.shape.interaction_events, + ), + exit: partialObservationFieldSchema( + z.strictObject({ + code: z.number().int().nullable(), + signal: z.number().int().nullable(), + reason: z.enum(["exited", "timeout", "idle_timeout", "cancelled"]), + }), + ), + settlement: partialObservationFieldSchema( + z.strictObject({ + state: z.enum(["quiesced", "alive_at_deadline", "unverifiable"]), + elapsed_ms: z.number().int().nonnegative(), + }), + ), + process_samples: partialObservationFieldSchema( + processCaptureShapeSchema.shape.process_samples, + ), + filesystem_snapshots: z.strictObject({ + before: partialObservationFieldSchema( + z.strictObject({ + files: z.array(fileStateSchema), + truncated: z.boolean(), + }), + ), + after: partialObservationFieldSchema( + z.strictObject({ + files: z.array(fileStateSchema), + truncated: z.boolean(), + }), + ), + }), + event_journal: partialObservationFieldSchema( + processCaptureShapeSchema.shape.event_journal, + ), + manifest: partialObservationFieldSchema( + processCaptureShapeSchema.shape.manifest, + ), +}); + +const partialCleanupReportSchema = z.strictObject({ + owned_process_group: processCleanupResourceSchema, + terminal_renderer: processCleanupResourceSchema, + temporary_root: processCleanupResourceSchema, +}); +const partialObservationMetadataSchema = z.strictObject({ + cleanup: partialCleanupReportSchema, + execution_failure: z.string().nullable(), +}); +const completedCaptureObservationSchema = z.strictObject({ + capture: processCaptureShapeSchema + .omit({ cleanup: true }) + .extend({ settlement: processSettlementSchema }), +}); +const incompleteCaptureObservationSchema = z.strictObject({ + observations: incompleteProcessCaptureObservationsSchema, +}); + +/** Validated, non-comparable process observations attached to cleanup errors. */ +export const partialProcessCaptureObservationSchema = z + .union([ + completedCaptureObservationSchema.extend( + partialObservationMetadataSchema.shape, + ), + incompleteCaptureObservationSchema.extend( + partialObservationMetadataSchema.shape, + ), + ]) + .superRefine((partial, context) => { + if ( + !Object.values(partial.cleanup).some( + ({ state }) => state === "failed" || state === "unverified", + ) + ) + context.addIssue({ + code: "custom", + path: ["cleanup"], + message: "partial observations require incomplete resource cleanup", + }); + + if ("observations" in partial) { + const { observations } = partial; + if ( + ![ + observations.frames, + observations.interaction_events, + observations.process_samples, + observations.filesystem_snapshots.before, + observations.event_journal, + ].some(({ state }) => state === "available") + ) + context.addIssue({ + code: "custom", + path: ["observations"], + message: "incomplete observations contain no collected evidence", + }); + return; + } + + const { capture } = partial; + const settlement = + capture.settlement.state === "quiesced" + ? { + ...capture.settlement, + cleanup_outcome: "not_required" as const, + } + : { ...capture.settlement, cleanup_outcome: "cleaned" as const }; + const validationCapture: UnverifiedProcessCapture = { + ...capture, + settlement, + cleanup: { + owned_process_group: "verified", + temporary_root: "removed", + }, + }; + for (const issue of collectProcessCaptureIssues(validationCapture)) + context.addIssue({ + code: "custom", + path: ["capture", ...issue.path.split(".")], + message: issue.message, + }); + }); + +/** One collected payload: completed capture data or explicit unavailable fields. */ +export type PartialProcessCaptureObservation = z.infer< + typeof partialProcessCaptureObservationSchema +>; + +/** Exact serialized shape plus all process-capture semantic invariants. */ +export const processCaptureSchema = processCaptureShapeSchema + .superRefine((capture, context) => { + if (capture.settlement.cleanup_outcome === "failed") + context.addIssue({ + code: "custom", + path: ["settlement", "cleanup_outcome"], + message: "verified captures cannot contain failed cleanup", + }); + for (const issue of collectProcessCaptureIssues(capture)) + context.addIssue({ + code: "custom", + path: issue.path.split("."), + message: issue.message, + }); + }) + .describe( + "The capture must preserve its canonical scenario, comparison, and normalization SHA-256 commitments; ordered capture timestamps and contiguous sequence numbers; before and final filesystem snapshots with truncation propagated; and exit-code consistency with deadline termination. When parsing older input without an event journal, REA supplies an empty journal; empty journals are valid. A non-empty journal must reference every captured observation exactly once with unique in-range references. These cross-field invariants are checked by REA after capture.", + ); + +export { parseProcessCapture } from "./processCaptureParsing.js"; +export type { ProcessCapture } from "./processCaptureParsing.js"; + +export { + compareProcessCaptures, + comparisonStatusSchema, + deriveProcessComparisonStatus, + PROCESS_COMPARISON_DIMENSIONS, + processCaptureComparisonSchema, +} from "./processComparison.js"; diff --git a/src/domain/processCapture.validation.test.ts b/src/domain/process/processCapture.validation.test.ts similarity index 65% rename from src/domain/processCapture.validation.test.ts rename to src/domain/process/processCapture.validation.test.ts index 0c7e899a8..912b7c16d 100644 --- a/src/domain/processCapture.validation.test.ts +++ b/src/domain/process/processCapture.validation.test.ts @@ -65,7 +65,7 @@ it("rejects altered v4 commitments and accepts canonical key reordering", () => executable_sha256: "f".repeat(64), }, }), - ).toThrow("executable_sha256"); + ).toThrow("executable_identity"); }); it("rejects settlement and cleanup combinations that cannot occur", () => { @@ -142,6 +142,92 @@ it("accepts old captures without a journal and validates complete journals", () } }); +it("migrates persisted v3 executable digests without claiming a selected digest", () => { + const capture = emptyCapture(); + const { + selected_executable_sha256: _selectedExecutableSha256, + executable_identity: _executableIdentity, + executable_sha256, + ...legacyManifest + } = capture.manifest; + const legacyCapture = { + ...capture, + manifest: { ...legacyManifest, executable_sha256 }, + }; + + const migrated = parseProcessCapture(legacyCapture); + + expect(migrated.manifest).toMatchObject({ + selected_executable_sha256: null, + executable_sha256: null, + executable_identity: { + state: "unknown", + reason: expect.stringContaining("did not distinguish"), + }, + legacy_executable_sha256: executable_sha256, + scenario: capture.manifest.scenario, + full_scenario_sha256: capture.manifest.full_scenario_sha256, + }); + expect(compareProcessCaptures(migrated, migrated)).toMatchObject({ + status: "unchanged", + }); + const exported = JSON.stringify(migrated); + expect(parseProcessCapture(JSON.parse(exported))).toEqual(migrated); + + expect(() => + parseProcessCapture({ + ...legacyCapture, + manifest: { + ...legacyCapture.manifest, + executable_sha256: "f".repeat(64), + }, + }), + ).toThrow("legacy_executable_sha256"); + + const nullDigestScenario = { + ...legacyCapture.manifest.scenario, + executable_sha256: null, + }; + const malformedLegacyBase = { + ...legacyCapture, + manifest: { + ...legacyCapture.manifest, + executable_sha256: null, + scenario: nullDigestScenario, + full_scenario_sha256: digestProcessCommitment(nullDigestScenario), + }, + }; + expect(() => parseProcessCapture(malformedLegacyBase)).toThrow(); + expect(() => + parseProcessCapture({ + ...malformedLegacyBase, + manifest: { + ...malformedLegacyBase.manifest, + executable_sha256: "not-a-digest", + }, + }), + ).toThrow(); + const { + executable_sha256: _missingLegacyDigest, + ...manifestWithoutLegacyDigest + } = malformedLegacyBase.manifest; + expect(() => + parseProcessCapture({ + ...malformedLegacyBase, + manifest: manifestWithoutLegacyDigest, + }), + ).toThrow(); + + const { executable_identity: _missingIdentity, ...incompleteModernManifest } = + capture.manifest; + expect(() => + parseProcessCapture({ + ...capture, + manifest: incompleteModernManifest, + }), + ).toThrow(); +}); + it("requires compatible contracts and enforces capture age through a clock seam", () => { const capture = emptyCapture(); expect(() => diff --git a/src/domain/processCaptureExample.ts b/src/domain/process/processCaptureExample.ts similarity index 89% rename from src/domain/processCaptureExample.ts rename to src/domain/process/processCaptureExample.ts index 72bc2e47f..d920dabdc 100644 --- a/src/domain/processCaptureExample.ts +++ b/src/domain/process/processCaptureExample.ts @@ -1,4 +1,4 @@ -import type { JsonValue } from "./jsonValue.js"; +import type { JsonValue } from "../jsonValue.js"; import { digestProcessCommitment } from "./processCapture.js"; const normalization = { @@ -25,7 +25,12 @@ export const EMPTY_PROCESS_CAPTURE_EXAMPLE = { comparison_contract: comparisonContract, full_scenario_sha256: digestProcessCommitment(scenario), comparison_contract_sha256: digestProcessCommitment(comparisonContract), + selected_executable_sha256: "0".repeat(64), executable_sha256: "0".repeat(64), + executable_identity: { + state: "path_metadata_unchanged", + reason: null, + }, normalization_sha256: digestProcessCommitment(normalization), }, normalization, diff --git a/src/domain/processCaptureParsing.ts b/src/domain/process/processCaptureParsing.ts similarity index 50% rename from src/domain/processCaptureParsing.ts rename to src/domain/process/processCaptureParsing.ts index dda070e59..018abf966 100644 --- a/src/domain/processCaptureParsing.ts +++ b/src/domain/process/processCaptureParsing.ts @@ -2,6 +2,7 @@ import { processCaptureSchema, type UnverifiedProcessCapture, } from "./processCapture.js"; +import { z } from "zod"; declare class ProcessCaptureProof { private readonly verified: never; @@ -22,9 +23,47 @@ const attachProof = (capture: UnverifiedProcessCapture): ProcessCapture => { throw new TypeError("Process Capture proof ownership failed"); }; +const recordSchema = z.record(z.string(), z.unknown()); +const LEGACY_EXECUTABLE_IDENTITY_REASON = + "Legacy v3 capture did not distinguish selected and launch executable digests"; + +const migrateLegacyV3Capture = (input: unknown): unknown => { + const capture = recordSchema.safeParse(input); + if (!capture.success) return input; + const manifest = recordSchema.safeParse(capture.data.manifest); + if (!manifest.success) return input; + if ( + manifest.data.provider_version !== "3" || + "selected_executable_sha256" in manifest.data || + "executable_identity" in manifest.data || + "legacy_executable_sha256" in manifest.data + ) + return input; + + const { executable_sha256 } = manifest.data; + if ( + typeof executable_sha256 !== "string" || + !/^[a-f0-9]{64}$/u.test(executable_sha256) + ) + return input; + return { + ...capture.data, + manifest: { + ...manifest.data, + selected_executable_sha256: null, + executable_sha256: null, + executable_identity: { + state: "unknown", + reason: LEGACY_EXECUTABLE_IDENTITY_REASON, + }, + legacy_executable_sha256: executable_sha256, + }, + }; +}; + /** Parse unknown input and reject invalid commitments or semantics. */ export const parseProcessCapture = (input: unknown): ProcessCapture => { - const parsed = processCaptureSchema.safeParse(input); + const parsed = processCaptureSchema.safeParse(migrateLegacyV3Capture(input)); if (parsed.success) return attachProof(parsed.data); const issues = parsed.error.issues.flatMap((issue) => issue.code === "custom" diff --git a/src/domain/processCaptureValidation.ts b/src/domain/process/processCaptureValidation.ts similarity index 80% rename from src/domain/processCaptureValidation.ts rename to src/domain/process/processCaptureValidation.ts index 29be8c6e8..041c4c122 100644 --- a/src/domain/processCaptureValidation.ts +++ b/src/domain/process/processCaptureValidation.ts @@ -26,8 +26,23 @@ const validateCommitments = ( require: RequireInvariant, ): void => { const { manifest } = capture; - require(manifest.scenario.executable_sha256 === - manifest.executable_sha256, "manifest.executable_sha256", "executable commitment does not match the scenario projection"); + if (manifest.legacy_executable_sha256 !== undefined) { + require(manifest.selected_executable_sha256 === null && + manifest.executable_sha256 === null && + manifest.executable_identity.state === "unknown" && + manifest.executable_identity.reason !== null && + manifest.scenario.executable_sha256 === + manifest.legacy_executable_sha256, "manifest.legacy_executable_sha256", "legacy executable digest must remain distinct from unknown selected and launch digests"); + } else { + require(manifest.scenario.executable_sha256 === + manifest.selected_executable_sha256, "manifest.selected_executable_sha256", "selected executable commitment does not match the scenario projection"); + } + require(manifest.executable_identity.state === "path_metadata_unchanged" + ? manifest.executable_sha256 === manifest.selected_executable_sha256 && + manifest.executable_identity.reason === null + : manifest.executable_sha256 === null && + manifest.executable_identity.reason !== + null, "manifest.executable_identity", "launch identity and executable digest do not agree"); for (const [field, value] of [ ["full_scenario_sha256", manifest.scenario], ["comparison_contract_sha256", manifest.comparison_contract], diff --git a/src/domain/processComparison.ts b/src/domain/process/processComparison.ts similarity index 99% rename from src/domain/processComparison.ts rename to src/domain/process/processComparison.ts index 7e69e0ae3..b6e5a8cc0 100644 --- a/src/domain/processComparison.ts +++ b/src/domain/process/processComparison.ts @@ -2,7 +2,7 @@ import canonicalize from "canonicalize"; import { z } from "zod"; import type { ProcessCapture } from "./processCapture.js"; -import { jsonValueSchema } from "./jsonValue.js"; +import { jsonValueSchema } from "../jsonValue.js"; import { compareProcessTraces, processTraceOutcomesDiffer, diff --git a/src/domain/processEvidenceProvider.ts b/src/domain/process/processEvidenceProvider.ts similarity index 90% rename from src/domain/processEvidenceProvider.ts rename to src/domain/process/processEvidenceProvider.ts index 233d95891..cd05abe07 100644 --- a/src/domain/processEvidenceProvider.ts +++ b/src/domain/process/processEvidenceProvider.ts @@ -1,4 +1,4 @@ -import type { EvidenceProvider } from "./evidence.js"; +import type { EvidenceProvider } from "../evidence.js"; /** Canonical provider identity for controlled process capture Evidence. */ export const PROCESS_PROVIDER = { diff --git a/src/domain/processObservation.test.ts b/src/domain/process/processObservation.test.ts similarity index 79% rename from src/domain/processObservation.test.ts rename to src/domain/process/processObservation.test.ts index 7ebf55a25..134938572 100644 --- a/src/domain/processObservation.test.ts +++ b/src/domain/process/processObservation.test.ts @@ -5,10 +5,7 @@ import { processCaptureSchema, type UnverifiedProcessCapture, } from "./processCapture.js"; -import { - createProcessObservation, - projectProcessObservation, -} from "./processObservation.js"; +import { projectProcessObservation } from "./processObservation.js"; const emptyCapture = processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE); @@ -86,25 +83,7 @@ describe("process observation vocabulary", () => { expect(observations[4]?.captured_at_ms).toBeNull(); }); - it("uses the same constructor for live and post-hoc observations", () => { - const location = { - collection: "frames" as const, - index: 0, - capture_order: 4, - }; - const projected = projectProcessObservation(capture, location); - const live = createProcessObservation({ - source: "terminal_raw", - source_sequence: 0, - captured_at_ms: 1, - subject_id: null, - location, - payload: capture.frames[0], - }); - expect(live).toEqual(projected); - }); - - it("returns no observation for a missing raw record", () => { + it("returns no observation for a missing collection record", () => { expect( projectProcessObservation(capture, { collection: "frames", diff --git a/src/domain/processObservation.ts b/src/domain/process/processObservation.ts similarity index 98% rename from src/domain/processObservation.ts rename to src/domain/process/processObservation.ts index 95d6fc9e9..167d36ec5 100644 --- a/src/domain/processObservation.ts +++ b/src/domain/process/processObservation.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { jsonValueSchema, type JsonValue } from "./jsonValue.js"; +import { jsonValueSchema, type JsonValue } from "../jsonValue.js"; import type { UnverifiedProcessCapture } from "./processCapture.js"; /** Process-capture observation families shared by live drivers and trace assertions. */ diff --git a/src/domain/processScenario.test.ts b/src/domain/process/processScenario.test.ts similarity index 84% rename from src/domain/processScenario.test.ts rename to src/domain/process/processScenario.test.ts index a90eb5985..9f669328d 100644 --- a/src/domain/processScenario.test.ts +++ b/src/domain/process/processScenario.test.ts @@ -61,6 +61,24 @@ describe("direct process scenarios", () => { ).toBe(true); }); + it("rejects NUL in strings passed to child-process APIs", () => { + const nul = "\0"; + for (const input of [ + { executable: `node${nul}` }, + { executable: "node", arguments: [nul] }, + { executable: "node", working_directory: `.${nul}` }, + { executable: "node", environment: { KEY: nul } }, + { executable: "node", filesystem_observation_paths: [nul] }, + ]) + expect(processScenarioSchema.safeParse(input).success).toBe(false); + expect( + processScenarioSchema.safeParse({ + executable: "node", + events: [{ type: "input", at_ms: 0, data: nul }], + }).success, + ).toBe(true); + }); + it("keeps bounded capture controls and ordered scheduled interaction", () => { const scenario = processScenarioSchema.parse({ executable: "node", diff --git a/src/domain/processScenario.ts b/src/domain/process/processScenario.ts similarity index 93% rename from src/domain/processScenario.ts rename to src/domain/process/processScenario.ts index b9924e036..382cf3196 100644 --- a/src/domain/processScenario.ts +++ b/src/domain/process/processScenario.ts @@ -14,6 +14,12 @@ const environmentName = z ? `${reservedRunIdEnvironmentName} is reserved by the process adapter` : "Environment names cannot contain '=' or NUL", }); +const childProcessString = z + .string() + .regex( + /^[^\0]*$/u, + "Values passed to operating-system APIs cannot contain NUL", + ); export const normalizationSchema = z.object({ paths: z.boolean(), pids: z.boolean(), @@ -86,11 +92,13 @@ export const processComparisonContract = ( */ export const processScenarioSchema = z .object({ - executable: z.string().min(1), - arguments: z.array(z.string()).default([]), - working_directory: z.string().default("."), - environment: z.record(environmentName, z.string()).default({}), - filesystem_observation_paths: z.array(z.string().min(1)).default([]), + executable: childProcessString.min(1), + arguments: z.array(childProcessString).default([]), + working_directory: childProcessString.default("."), + environment: z.record(environmentName, childProcessString).default({}), + filesystem_observation_paths: z + .array(childProcessString.min(1)) + .default([]), terminal: z .object({ columns: z.number().int().min(1).max(65_535).default(80), diff --git a/src/domain/processTraceAdapters.test.ts b/src/domain/process/processTraceAdapters.test.ts similarity index 100% rename from src/domain/processTraceAdapters.test.ts rename to src/domain/process/processTraceAdapters.test.ts diff --git a/src/domain/processTraceComparison.fixture.ts b/src/domain/process/processTraceComparison.fixture.ts similarity index 100% rename from src/domain/processTraceComparison.fixture.ts rename to src/domain/process/processTraceComparison.fixture.ts diff --git a/src/domain/processTraceComparison.test.ts b/src/domain/process/processTraceComparison.test.ts similarity index 100% rename from src/domain/processTraceComparison.test.ts rename to src/domain/process/processTraceComparison.test.ts diff --git a/src/domain/processTraceComparison.ts b/src/domain/process/processTraceComparison.ts similarity index 100% rename from src/domain/processTraceComparison.ts rename to src/domain/process/processTraceComparison.ts diff --git a/src/domain/processTraceDimensionProjection.ts b/src/domain/process/processTraceDimensionProjection.ts similarity index 100% rename from src/domain/processTraceDimensionProjection.ts rename to src/domain/process/processTraceDimensionProjection.ts diff --git a/src/domain/processTraceEvaluation.ts b/src/domain/process/processTraceEvaluation.ts similarity index 99% rename from src/domain/processTraceEvaluation.ts rename to src/domain/process/processTraceEvaluation.ts index bd4569ffc..5b61cb1c0 100644 --- a/src/domain/processTraceEvaluation.ts +++ b/src/domain/process/processTraceEvaluation.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import type { JsonValue } from "./jsonValue.js"; +import type { JsonValue } from "../jsonValue.js"; import type { ProcessCapture } from "./processCapture.js"; import { processObservationLocationSchema, diff --git a/src/domain/processTraceSpecification.test.ts b/src/domain/process/processTraceSpecification.test.ts similarity index 100% rename from src/domain/processTraceSpecification.test.ts rename to src/domain/process/processTraceSpecification.test.ts diff --git a/src/domain/processTraceSpecification.ts b/src/domain/process/processTraceSpecification.ts similarity index 99% rename from src/domain/processTraceSpecification.ts rename to src/domain/process/processTraceSpecification.ts index 75e129e47..5c70dc4a2 100644 --- a/src/domain/processTraceSpecification.ts +++ b/src/domain/process/processTraceSpecification.ts @@ -1,7 +1,7 @@ import canonicalize from "canonicalize"; import { z } from "zod"; -import { jsonValueSchema } from "./jsonValue.js"; +import { jsonValueSchema } from "../jsonValue.js"; import { comparableProcessObservationPayload, processObservationSourceSchema, diff --git a/src/domain/processCapture.comparison.test.ts b/src/domain/processCapture.comparison.test.ts deleted file mode 100644 index 431fef24e..000000000 --- a/src/domain/processCapture.comparison.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { expect, it } from "vitest"; - -import { - compareUnverifiedProcessCaptures as compareProcessCaptures, - emptyProcessCapture as emptyCapture, -} from "./processCapture.fixture.js"; - -it("classifies missing observations as unknown and one-sided evidence as added", () => { - const base = { - manifest: emptyCapture().manifest, - settlement: emptyCapture().settlement, - normalization: { - paths: true, - pids: true, - ports: true, - time_bucket_ms: 10, - patterns: [], - }, - frames: [], - rendered_frames: [], - interaction_events: [], - exit: { code: 0, signal: null, reason: "exited" as const }, - process_samples: [], - filesystem_checkpoints: emptyCapture().filesystem_checkpoints, - files_before: [], - files_after: [], - filesystem_effects: [], - truncated: false, - limitations: [], - residual_unknowns: [], - cleanup: { - owned_process_group: "verified" as const, - temporary_root: "removed" as const, - }, - }; - const added = compareProcessCaptures(base, { - ...base, - frames: [{ sequence: 0, at_ms: 0, data: "new" }], - rendered_frames: [ - { - sequence: 0, - at_ms: 0, - columns: 3, - rows: 1, - cursor_x: 3, - cursor_y: 0, - active_buffer: "normal", - lines: ["new"], - serialized_state: "new", - }, - ], - }); - expect(added.terminal).toBe("added"); - expect(added.status).toBe("changed"); - const unknown = compareProcessCaptures( - { ...base, residual_unknowns: [{ scope: "process", reason: "sampled" }] }, - base, - ); - expect(unknown.process).toBe("unknown"); - expect(unknown.status).toBe("unknown"); -}); - -it("treats equivalent normalization records as equal regardless of member order", () => { - const capture = emptyCapture(); - - expect( - compareProcessCaptures(capture, { - ...capture, - normalization: { - patterns: [], - time_bucket_ms: 10, - ports: true, - pids: true, - paths: true, - }, - }), - ).toMatchObject({ - status: "unchanged", - first_divergence: { status: "none" }, - }); -}); - -it("compares raw terminal chunks even when rendered states agree", () => { - const capture = { - manifest: emptyCapture().manifest, - settlement: emptyCapture().settlement, - normalization: { - paths: true, - pids: true, - ports: true, - time_bucket_ms: 10, - patterns: [], - }, - frames: [{ sequence: 0, at_ms: 0, data: "bar" }], - rendered_frames: [], - interaction_events: [], - exit: { code: 0, signal: null, reason: "exited" as const }, - process_samples: [], - filesystem_checkpoints: emptyCapture().filesystem_checkpoints, - files_before: [], - files_after: [], - filesystem_effects: [], - truncated: false, - limitations: [], - residual_unknowns: [], - cleanup: { - owned_process_group: "verified" as const, - temporary_root: "removed" as const, - }, - }; - - const comparison = compareProcessCaptures(capture, { - ...capture, - frames: [{ sequence: 0, at_ms: 0, data: "foo\rbar" }], - }); - expect(comparison.terminal).toBe("changed"); - expect(comparison.first_divergence).toMatchObject({ - status: "found", - dimension: "terminal", - }); -}); - -it("keeps filesystem evidence unknown when stable snapshots match", () => { - const capture = { - manifest: emptyCapture().manifest, - settlement: emptyCapture().settlement, - normalization: { - paths: true, - pids: true, - ports: true, - time_bucket_ms: 10, - patterns: [], - }, - frames: [], - rendered_frames: [], - interaction_events: [], - exit: { code: 0, signal: null, reason: "exited" as const }, - process_samples: [], - filesystem_checkpoints: emptyCapture().filesystem_checkpoints, - files_before: [], - files_after: [], - filesystem_effects: [], - truncated: false, - limitations: [], - residual_unknowns: [ - { scope: "filesystem" as const, reason: "watcher unavailable" }, - ], - cleanup: { - owned_process_group: "verified" as const, - temporary_root: "removed" as const, - }, - }; - - const comparison = compareProcessCaptures(capture, capture); - expect(comparison.filesystem).toBe("unknown"); - expect(comparison.status).toBe("unknown"); - - const complete = { ...capture, residual_unknowns: [] }; - const finalFile = { - path: "root_0:created.txt", - type: "file" as const, - mode: 0o644, - size: 4, - sha256: "a".repeat(64), - symlink_target: null, - }; - const changed = compareProcessCaptures(complete, { - ...complete, - filesystem_checkpoints: [ - { name: "before", at_ms: 0, files: [], effects: [], truncated: false }, - { - name: "after_settlement", - at_ms: 10, - files: [finalFile], - effects: [ - { - path: finalFile.path, - status: "created", - before: null, - after: finalFile, - }, - ], - truncated: false, - }, - ], - files_after: [finalFile], - filesystem_effects: [ - { - path: finalFile.path, - status: "created", - before: null, - after: finalFile, - }, - ], - }); - expect(changed.filesystem).toBe("changed"); - expect(changed.status).toBe("changed"); -}); diff --git a/src/domain/processCapture.traceProjection.test.ts b/src/domain/processCapture.traceProjection.test.ts deleted file mode 100644 index b6b77917a..000000000 --- a/src/domain/processCapture.traceProjection.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { expect, it } from "vitest"; - -import { - compareUnverifiedProcessCaptures as compareProcessCaptures, - emptyProcessCapture as emptyCapture, -} from "./processCapture.fixture.js"; - -it("detects changes in normalized process sample metadata", () => { - const capture = { - manifest: emptyCapture().manifest, - settlement: emptyCapture().settlement, - normalization: { - paths: true, - pids: true, - ports: true, - time_bucket_ms: 10, - patterns: [], - }, - frames: [], - rendered_frames: [], - interaction_events: [], - exit: { code: 0, signal: null, reason: "exited" as const }, - process_samples: [ - { - at_ms: 10, - pid: 1, - parent_pid: 0, - process_group_id: 1, - session_id: 1, - command: "worker", - }, - ], - filesystem_checkpoints: emptyCapture().filesystem_checkpoints, - files_before: [], - files_after: [], - filesystem_effects: [], - truncated: false, - limitations: [], - residual_unknowns: [], - cleanup: { - owned_process_group: "verified" as const, - temporary_root: "removed" as const, - }, - }; - const changed = { - ...capture, - process_samples: [ - { - at_ms: 20, - pid: 1, - parent_pid: 2, - process_group_id: 1, - session_id: 1, - command: "worker", - }, - ], - }; - - const comparison = compareProcessCaptures(capture, changed); - expect(comparison.process).toBe("changed"); - expect(comparison.status).toBe("changed"); -}); diff --git a/src/domain/processCapture.ts b/src/domain/processCapture.ts deleted file mode 100644 index 76d888c70..000000000 --- a/src/domain/processCapture.ts +++ /dev/null @@ -1,398 +0,0 @@ -import { z } from "zod"; -import { jsonValueSchema } from "./jsonValue.js"; - -import { normalizationSchema } from "./processScenario.js"; -import { collectProcessCaptureIssues } from "./processCaptureValidation.js"; - -export * from "./processScenario.js"; - -/** Normalized raw PTY chunk, preserving transport-level output differences. */ -export interface TerminalFrame { - readonly sequence: number; - readonly at_ms: number; - readonly data: string; -} - -/** Serialized terminal state after interpreting control and resize sequences. */ -export interface RenderedTerminalFrame { - readonly sequence: number; - readonly at_ms: number; - readonly columns: number; - readonly rows: number; - readonly cursor_x: number; - readonly cursor_y: number; - readonly active_buffer: "normal" | "alternate"; - readonly lines: readonly string[]; - readonly serialized_state: string; -} - -/** Scheduled terminal interaction with its observed dispatch outcome. */ -export interface InteractionEvent { - readonly sequence: number; - readonly scheduled_at_ms: number; - readonly dispatched_at_ms: number; - readonly type: "input" | "resize" | "signal"; - readonly data: string; - readonly outcome: "dispatched" | "target_exited" | "failed"; -} - -/** One filesystem state used for before/after comparison. */ -interface FileStateIdentity { - readonly path: string; - readonly mode: number; - readonly size: number; -} - -export type FileState = FileStateIdentity & - ( - | { - readonly type: "file"; - readonly sha256: string | null; - readonly symlink_target: null; - } - | { - readonly type: "symlink"; - readonly sha256: null; - readonly symlink_target: string; - } - | { - readonly type: "directory" | "other"; - readonly sha256: null; - readonly symlink_target: null; - } - ); - -interface FileEffectIdentity { - readonly path: string; -} - -type FileEffect = FileEffectIdentity & - ( - | { - readonly status: "created"; - readonly before: null; - readonly after: FileState; - } - | { - readonly status: "deleted"; - readonly before: FileState; - readonly after: null; - } - | { - readonly status: "modified" | "unchanged"; - readonly before: FileState; - readonly after: FileState; - } - ); - -/** A sampled owned-process observation; sampling cannot prove syscall completeness. */ -export interface ProcessSample { - readonly at_ms: number; - readonly pid: number; - readonly parent_pid: number; - readonly command: string; - readonly process_group_id: number | null; - readonly session_id: number | null; -} - -/** Initial or final filesystem snapshot selected for observation. */ -export interface FilesystemCheckpoint { - readonly name: string; - readonly at_ms: number; - readonly files: readonly FileState[]; - readonly effects: readonly FileEffect[]; - readonly truncated: boolean; -} - -/** Capture collection whose members participate in global observation order. */ -export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [ - "frames", - "rendered_frames", - "interaction_events", - "lifecycle", - "process_samples", - "filesystem_checkpoints", -] as const; - -export type ProcessCaptureEventCollection = - (typeof PROCESS_CAPTURE_EVENT_COLLECTIONS)[number]; - -/** One reference from global observation order into a capture collection. */ -export interface ProcessCaptureEventJournalEntry { - readonly capture_order: number; - readonly collection: ProcessCaptureEventCollection; - readonly index: number; -} - -/** Shared observation callback used by every process-capture producer. */ -export type RecordProcessCaptureEvent = ( - collection: ProcessCaptureEventCollection, - index: number, -) => void; - -/** Observed process-tree settlement and the cleanup required by that state. */ -export type ProcessSettlement = - | { - readonly state: "quiesced"; - readonly elapsed_ms: number; - readonly cleanup_outcome: "not_required"; - } - | { - readonly state: "alive_at_deadline" | "unverifiable"; - readonly elapsed_ms: number; - readonly cleanup_outcome: "cleaned" | "failed"; - }; - -/** - * Process capture observation set. - * - * `truncated` and `residual_unknowns` are semantic evidence: consumers must not - * infer equivalence from matching bounded observations when either is present. - */ -export interface UnverifiedProcessCapture { - readonly manifest: { - readonly rea_version: string; - readonly provider_version: string; - readonly platform: string; - readonly architecture: string; - readonly pty_backend: "node-pty"; - readonly started_at: string; - readonly completed_at: string; - readonly scenario: Readonly>; - readonly comparison_contract: Readonly>; - readonly full_scenario_sha256: string; - readonly comparison_contract_sha256: string; - readonly executable_sha256: string; - readonly normalization_sha256: string; - }; - readonly normalization: z.infer; - readonly frames: readonly TerminalFrame[]; - readonly rendered_frames: readonly RenderedTerminalFrame[]; - readonly interaction_events: readonly InteractionEvent[]; - readonly exit: { - readonly code: number | null; - readonly signal: number | null; - readonly reason: "exited" | "timeout" | "idle_timeout"; - }; - readonly settlement: ProcessSettlement; - readonly process_samples: readonly ProcessSample[]; - readonly filesystem_checkpoints: readonly FilesystemCheckpoint[]; - /** - * Global observation order across independently recorded collections. - * - * Absence is accepted for captures written before this journal existed. - */ - readonly event_journal?: readonly ProcessCaptureEventJournalEntry[]; - readonly files_before: readonly FileState[]; - readonly files_after: readonly FileState[]; - readonly filesystem_effects: readonly FileEffect[]; - readonly truncated: boolean; - readonly limitations: readonly string[]; - readonly residual_unknowns: readonly { - readonly scope: - | "terminal" - | "interaction" - | "exit" - | "process" - | "filesystem" - | "cleanup" - | "network" - | "environment"; - readonly reason: string; - }[]; - readonly cleanup: { - readonly owned_process_group: "verified"; - readonly temporary_root: "removed"; - }; -} - -const fileStateShape = { - path: z.string(), - mode: z.number().int().nonnegative(), - size: z.number().int().nonnegative(), -}; -const fileStateSchema = z.discriminatedUnion("type", [ - z.object({ - ...fileStateShape, - type: z.literal("file"), - sha256: z - .string() - .regex(/^[a-f0-9]{64}$/u) - .nullable(), - symlink_target: z.null(), - }), - z.object({ - ...fileStateShape, - type: z.literal("symlink"), - sha256: z.null(), - symlink_target: z.string(), - }), - z.object({ - ...fileStateShape, - type: z.enum(["directory", "other"]), - sha256: z.null(), - symlink_target: z.null(), - }), -]); -const fileEffectSchema = z.discriminatedUnion("status", [ - z.object({ - path: z.string(), - status: z.literal("created"), - before: z.null(), - after: fileStateSchema, - }), - z.object({ - path: z.string(), - status: z.literal("deleted"), - before: fileStateSchema, - after: z.null(), - }), - z.object({ - path: z.string(), - status: z.enum(["modified", "unchanged"]), - before: fileStateSchema, - after: fileStateSchema, - }), -]); -/** Exact serialized shape of a process capture. */ -const processCaptureShapeSchema: z.ZodType = - z.strictObject({ - manifest: z.strictObject({ - rea_version: z.string().min(1), - provider_version: z.string().min(1), - platform: z.string().min(1), - architecture: z.string().min(1), - pty_backend: z.literal("node-pty"), - started_at: z.iso.datetime(), - completed_at: z.iso.datetime(), - scenario: z.record(z.string(), jsonValueSchema), - comparison_contract: z.record(z.string(), jsonValueSchema), - full_scenario_sha256: z.string().regex(/^[a-f0-9]{64}$/u), - comparison_contract_sha256: z.string().regex(/^[a-f0-9]{64}$/u), - executable_sha256: z.string().regex(/^[a-f0-9]{64}$/u), - normalization_sha256: z.string().regex(/^[a-f0-9]{64}$/u), - }), - normalization: normalizationSchema, - frames: z.array( - z.object({ - sequence: z.number().int().nonnegative(), - at_ms: z.number().int().nonnegative(), - data: z.string(), - }), - ), - rendered_frames: z.array( - z.object({ - sequence: z.number().int().nonnegative(), - at_ms: z.number().int().nonnegative(), - columns: z.number().int().positive(), - rows: z.number().int().positive(), - cursor_x: z.number().int().nonnegative(), - cursor_y: z.number().int().nonnegative(), - active_buffer: z.enum(["normal", "alternate"]), - lines: z.array(z.string()), - serialized_state: z.string(), - }), - ), - interaction_events: z.array( - z.object({ - sequence: z.number().int().nonnegative(), - scheduled_at_ms: z.number().int().nonnegative(), - dispatched_at_ms: z.number().int().nonnegative(), - type: z.enum(["input", "resize", "signal"]), - data: z.string(), - outcome: z.enum(["dispatched", "target_exited", "failed"]), - }), - ), - exit: z.object({ - code: z.number().int().nullable(), - signal: z.number().int().nullable(), - reason: z.enum(["exited", "timeout", "idle_timeout"]), - }), - settlement: z.discriminatedUnion("state", [ - z.object({ - state: z.literal("quiesced"), - elapsed_ms: z.number().int().nonnegative(), - cleanup_outcome: z.literal("not_required"), - }), - z.object({ - state: z.enum(["alive_at_deadline", "unverifiable"]), - elapsed_ms: z.number().int().nonnegative(), - cleanup_outcome: z.enum(["cleaned", "failed"]), - }), - ]), - process_samples: z.array( - z.object({ - at_ms: z.number().int().nonnegative(), - pid: z.number().int().positive(), - parent_pid: z.number().int().nonnegative(), - command: z.string(), - process_group_id: z.number().int().positive().nullable(), - session_id: z.number().int().nonnegative().nullable(), - }), - ), - filesystem_checkpoints: z.array( - z.object({ - name: z.enum(["before", "after_settlement"]), - at_ms: z.number().int().nonnegative(), - files: z.array(fileStateSchema), - effects: z.array(fileEffectSchema), - truncated: z.boolean(), - }), - ), - event_journal: z - .array( - z.object({ - capture_order: z.number().int().nonnegative(), - collection: z.enum(PROCESS_CAPTURE_EVENT_COLLECTIONS), - index: z.number().int().nonnegative(), - }), - ) - .default([]), - files_before: z.array(fileStateSchema), - files_after: z.array(fileStateSchema), - filesystem_effects: z.array(fileEffectSchema), - truncated: z.boolean(), - limitations: z.array(z.string()), - residual_unknowns: z.array( - z.object({ - scope: z.enum([ - "terminal", - "interaction", - "exit", - "process", - "filesystem", - "cleanup", - "network", - "environment", - ]), - reason: z.string(), - }), - ), - cleanup: z.object({ - owned_process_group: z.literal("verified"), - temporary_root: z.literal("removed"), - }), - }); - -/** Exact serialized shape plus all process-capture semantic invariants. */ -export const processCaptureSchema = processCaptureShapeSchema.superRefine( - (capture, context) => { - for (const issue of collectProcessCaptureIssues(capture)) - context.addIssue({ - code: "custom", - path: issue.path.split("."), - message: issue.message, - }); - }, -); - -export { parseProcessCapture } from "./processCaptureParsing.js"; -export type { ProcessCapture } from "./processCaptureParsing.js"; - -export { - compareProcessCaptures, - comparisonStatusSchema, - deriveProcessComparisonStatus, - PROCESS_COMPARISON_DIMENSIONS, - processCaptureComparisonSchema, -} from "./processComparison.js"; diff --git a/src/domain/processEvidenceProvider.test.ts b/src/domain/processEvidenceProvider.test.ts deleted file mode 100644 index 637dd36e6..000000000 --- a/src/domain/processEvidenceProvider.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect, it } from "vitest"; - -import { - PROCESS_PROVIDER, - isProcessEvidenceProvider, -} from "./processEvidenceProvider.js"; - -it.each(["REA process capture", "REA deterministic process harness"])( - "accepts the exact v3 process provider name %s", - (name) => { - expect(isProcessEvidenceProvider({ ...PROCESS_PROVIDER, name })).toBe(true); - }, -); - -it.each([ - { name: "REA process capture extra" }, - { name: "REA deterministic process harness extra" }, - { name: "rea process capture" }, - { name: "REA Process Capture" }, - { name: " REA process capture" }, - { name: "REA process capture " }, - { name: "REA deterministic process harness " }, - { name: "Fixture process provider" }, - { id: "fixture-process" }, - { version: "2" }, - { version: "4" }, - { version: null }, -])("rejects other process identities: %j", (change) => { - expect(isProcessEvidenceProvider({ ...PROCESS_PROVIDER, ...change })).toBe( - false, - ); -}); diff --git a/src/domain/protocolCapture.test.ts b/src/domain/protocolCapture.test.ts deleted file mode 100644 index ba5818954..000000000 --- a/src/domain/protocolCapture.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { readFile } from "node:fs/promises"; -import { describe, expect, it } from "vitest"; - -import { - classifyProtocolFamily, - decodeGrpcFrame, - decodeJsonRpc, - decodeMessagePack, - inferMessageSchema, - protocolCaptureSchema, - type ProtocolMessage, -} from "./protocolCapture.js"; - -describe("protocol capture", () => { - it("accepts every captured message", () => { - const message: ProtocolMessage = { - sequence: 0, - at_ms: 0, - family: "json-rpc", - direction: "request", - endpoint: null, - content_type: null, - raw_payload: null, - decoded_fields: [], - schema_hypotheses: [], - truncated: false, - credentials_redacted: false, - }; - - expect( - protocolCaptureSchema.parse({ - family: "json-rpc", - messages: Array.from({ length: 10_001 }, (_, sequence) => ({ - ...message, - sequence, - })), - }).messages, - ).toHaveLength(10_001); - }); - - it("classifies gRPC from content type", () => { - expect(classifyProtocolFamily("application/grpc+proto", null)).toBe("grpc"); - }); - - it("classifies protobuf from content type", () => { - expect(classifyProtocolFamily("application/x-protobuf", null)).toBe( - "protobuf", - ); - }); - - it("classifies json-rpc from content type", () => { - expect(classifyProtocolFamily("application/json-rpc", null)).toBe( - "json-rpc", - ); - }); - - it("classifies msgpack from content type", () => { - expect(classifyProtocolFamily("application/msgpack", null)).toBe( - "messagepack", - ); - }); - - it("classifies from path when content type is null", () => { - expect(classifyProtocolFamily(null, "/some/grpc/method")).toBe("grpc"); - expect(classifyProtocolFamily(null, "/api/json-rpc/2.0")).toBe("json-rpc"); - }); - - it("returns null for unknown protocols", () => { - expect(classifyProtocolFamily("text/plain", null)).toBeNull(); - expect(classifyProtocolFamily(null, null)).toBeNull(); - }); -}); - -describe("JSON-RPC decoding", () => { - it("decodes the captured MCP SDK initialize request with exact field values", async () => { - const payload = await readFile( - new URL( - "../../tests/fixtures/golden/mcp-initialize.json", - import.meta.url, - ), - ); - expect(decodeJsonRpc(payload)).toEqual({ - decoded_fields: [ - { - path: "method", - wire_type: "json", - value: "initialize", - inferred: false, - }, - { - path: "params", - wire_type: "json", - inferred: false, - value: { - protocolVersion: "2025-11-25", - capabilities: {}, - clientInfo: { name: "rea-protocol-golden", version: "1" }, - }, - }, - { path: "jsonrpc", wire_type: "json", value: "2.0", inferred: false }, - { path: "id", wire_type: "json", value: 0, inferred: false }, - ], - truncated: false, - }); - }); - - it("preserves decoded fields whose names resemble credentials", () => { - const payload = new TextEncoder().encode( - JSON.stringify({ - password: "observed-value", - authorization: { scheme: "custom", value: "observed-header" }, - username: "admin", - }), - ); - const result = decodeJsonRpc(payload); - expect(result.decoded_fields).toEqual([ - { - path: "password", - wire_type: "json", - value: "observed-value", - inferred: false, - }, - { - path: "authorization", - wire_type: "json", - value: { scheme: "custom", value: "observed-header" }, - inferred: false, - }, - { - path: "username", - wire_type: "json", - value: "admin", - inferred: false, - }, - ]); - }); -}); - -describe("MessagePack decoding", () => { - it("does not report unobserved payload bytes as truncation", () => { - const result = decodeMessagePack(new Uint8Array(10_001).fill(0x82)); - - expect(result.truncated).toBe(false); - }); - - it("detects map type", () => { - const payload = new Uint8Array([0x82]); - const result = decodeMessagePack(payload); - expect(result.decoded_fields[0]?.value).toBe("map"); - }); - - it("detects array type", () => { - const payload = new Uint8Array([0x92]); - const result = decodeMessagePack(payload); - expect(result.decoded_fields[0]?.value).toBe("array"); - }); - - it("reports unknown type for other tags", () => { - const payload = new Uint8Array([0xff]); - const result = decodeMessagePack(payload); - expect(result.decoded_fields[0]?.value).toBe("unknown"); - expect(result.decoded_fields[0]?.inferred).toBe(true); - }); -}); - -describe("gRPC frame decoding", () => { - it("decodes a valid gRPC frame header", () => { - const payload = new Uint8Array([ - 0x00, 0x00, 0x00, 0x00, 0x05, 1, 2, 3, 4, 5, - ]); - const result = decodeGrpcFrame(payload); - expect(result.decoded_fields).toHaveLength(2); - expect(result.decoded_fields[0]?.path).toBe("compressed"); - expect(result.decoded_fields[0]?.value).toBe(false); - expect(result.decoded_fields[1]?.path).toBe("message_length"); - expect(result.decoded_fields[1]?.value).toBe(5); - }); - - it("detects compressed flag", () => { - const payload = new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x0a]); - const result = decodeGrpcFrame(payload); - expect(result.decoded_fields[0]?.value).toBe(true); - }); - - it("handles too-short frames", () => { - const payload = new Uint8Array([0x00, 0x01]); - const result = decodeGrpcFrame(payload); - expect(result.decoded_fields[0]?.path).toBe("error"); - }); -}); - -describe("schema inference", () => { - it("infers schema from repeated messages", () => { - const messages: ProtocolMessage[] = [ - { - sequence: 0, - at_ms: 0, - family: "json-rpc", - direction: "request", - endpoint: "/api", - content_type: "application/json", - raw_payload: null, - decoded_fields: [ - { path: "method", wire_type: "json", value: "add", inferred: false }, - { path: "id", wire_type: "json", value: 1, inferred: false }, - ], - schema_hypotheses: [], - truncated: false, - credentials_redacted: false, - }, - { - sequence: 1, - at_ms: 10, - family: "json-rpc", - direction: "response", - endpoint: "/api", - content_type: "application/json", - raw_payload: null, - decoded_fields: [ - { path: "result", wire_type: "json", value: 3, inferred: false }, - { path: "id", wire_type: "json", value: 1, inferred: false }, - ], - schema_hypotheses: [], - truncated: false, - credentials_redacted: false, - }, - ]; - const hypotheses = inferMessageSchema(messages); - expect(hypotheses).toHaveLength(3); - const idHypothesis = hypotheses.find((h) => h.field_name === "id"); - expect(idHypothesis).toBeDefined(); - expect(idHypothesis!.evidence_count).toBe(2); - expect(idHypothesis!.confidence).toBe(1); - }); -}); - -describe("protocol capture schema validation", () => { - it("rejects a capture with an unknown protocol family", () => { - const result = protocolCaptureSchema.safeParse({ - family: "smtp", - messages: [], - inferred_schema: [], - has_truncated: false, - credentials_detected: false, - }); - expect(result.success).toBe(false); - }); - - it("rejects a capture that declares a non-boolean truncation flag", () => { - const result = protocolCaptureSchema.safeParse({ - family: "grpc", - messages: [], - inferred_schema: [], - has_truncated: "no", - credentials_detected: false, - }); - expect(result.success).toBe(false); - }); -}); diff --git a/src/domain/protocolCapture.ts b/src/domain/protocolCapture.ts deleted file mode 100644 index dbb60bedb..000000000 --- a/src/domain/protocolCapture.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { z } from "zod"; - -import { jsonValueSchema } from "./jsonValue.js"; - -/** Supported protocol families for structured protocol capture. */ -export const protocolFamilySchema = z.enum([ - "grpc", - "protobuf", - "json-rpc", - "messagepack", -]); -export type ProtocolFamily = z.infer; - -/** A decoded field from a protocol message. */ -export const decodedFieldSchema = z.strictObject({ - /** Field name or path within the message structure. */ - path: z.string().min(1), - /** Wire type or encoding hint. */ - wire_type: z.string().nullable(), - /** Decoded field value. */ - value: jsonValueSchema.nullable(), - /** Whether this field value was inferred rather than decoded. */ - inferred: z.boolean().default(false), -}); -export type DecodedField = z.infer; - -/** Schema inference hypothesis for a repeated message pattern. */ -export const schemaHypothesisSchema = z.strictObject({ - /** Hypothesized field name. */ - field_name: z.string().min(1), - /** Inferred type. */ - inferred_type: z.string().min(1), - /** Confidence: 0-1 */ - confidence: z.number().min(0).max(1), - /** Supporting evidence count. */ - evidence_count: z.number().int().nonnegative(), -}); -export type SchemaHypothesis = z.infer; - -/** Result of decoding a single protocol message. */ -export const protocolMessageSchema = z.strictObject({ - /** Monotonic sequence number. */ - sequence: z.number().int().nonnegative(), - /** Timestamp in milliseconds. */ - at_ms: z.number().int().nonnegative(), - /** Protocol family. */ - family: protocolFamilySchema, - /** Direction of the message. */ - direction: z.enum(["request", "response"]), - /** Transport endpoint or path (e.g. gRPC method path). */ - endpoint: z.string().nullable(), - /** Content type of the raw payload. */ - content_type: z.string().nullable(), - /** Raw payload bytes as base64. */ - raw_payload: z.string().nullable(), - /** Decoded fields from the payload. */ - decoded_fields: z.array(decodedFieldSchema).default([]), - /** Inferred schema hypotheses. */ - schema_hypotheses: z.array(schemaHypothesisSchema).default([]), - /** Whether decoding was truncated. */ - truncated: z.boolean().default(false), - /** Whether the capture producer marked credentials as redacted. */ - credentials_redacted: z.boolean().default(false), -}); -export type ProtocolMessage = z.infer; - -/** A captured protocol session with messages and inferred schemas. */ -export const protocolCaptureSchema = z.strictObject({ - /** Protocol family for this capture. */ - family: protocolFamilySchema, - /** Captured messages in order. */ - messages: z.array(protocolMessageSchema).min(0), - /** Aggregated schema hypotheses across all messages. */ - inferred_schema: z.array(schemaHypothesisSchema).default([]), - /** Whether any message was truncated. */ - has_truncated: z.boolean().default(false), - /** Whether the capture producer marked credential content as redacted. */ - credentials_detected: z.boolean().default(false), -}); - -/** Classification of a byte sequence as a known protocol family. */ -export function classifyProtocolFamily( - contentType: string | null, - path: string | null, -): ProtocolFamily | null { - if (contentType) { - if (contentType.includes("application/grpc")) return "grpc"; - if (contentType.includes("application/x-protobuf")) return "protobuf"; - if (contentType.includes("application/json-rpc")) return "json-rpc"; - if ( - contentType.includes("application/msgpack") || - contentType.includes("application/x-msgpack") - ) - return "messagepack"; - } - if (path) { - if (path.startsWith("/grpc.") || path.includes("/grpc/")) return "grpc"; - if (path.includes("/json-rpc") || path.includes("/jsonrpc")) - return "json-rpc"; - } - return null; -} - -/** Decode a JSON-RPC message from raw payload bytes. */ -export function decodeJsonRpc(rawPayload: Uint8Array): { - decoded_fields: DecodedField[]; - truncated: boolean; -} { - const text = new TextDecoder().decode(rawPayload); - const parsed = jsonValueSchema.parse(JSON.parse(text)); - const fields: DecodedField[] = []; - - if (typeof parsed === "object" && parsed !== null) { - for (const [key, value] of Object.entries(parsed)) { - fields.push({ - path: key, - wire_type: "json", - value, - inferred: false, - }); - } - } - - return { decoded_fields: fields, truncated: false }; -} - -/** Decode a MessagePack message (simplified: only checks basic structure). */ -export function decodeMessagePack(rawPayload: Uint8Array): { - decoded_fields: DecodedField[]; - truncated: boolean; -} { - const fields: DecodedField[] = []; - // MessagePack uses a single-byte type tag - if (rawPayload.length === 0) { - return { decoded_fields: [], truncated: false }; - } - const tag = rawPayload[0]; - if (tag === undefined) return { decoded_fields: [], truncated: false }; - // Map type: 0x80-0x8f (fixmap), 0xde (map16), 0xdf (map32) - if ((tag >= 0x80 && tag <= 0x8f) || tag === 0xde || tag === 0xdf) { - fields.push({ - path: "type", - wire_type: "msgpack", - value: "map", - inferred: false, - }); - } - // Array type: 0x90-0x9f (fixarray), 0xdc (array16), 0xdd (array32) - else if ((tag >= 0x90 && tag <= 0x9f) || tag === 0xdc || tag === 0xdd) { - fields.push({ - path: "type", - wire_type: "msgpack", - value: "array", - inferred: false, - }); - } else { - fields.push({ - path: "type", - wire_type: "msgpack", - value: "unknown", - inferred: true, - }); - } - return { - decoded_fields: fields, - truncated: false, - }; -} - -/** Decode a gRPC frame (5-byte header + protobuf payload). */ -export function decodeGrpcFrame(rawPayload: Uint8Array): { - decoded_fields: DecodedField[]; - truncated: boolean; -} { - // gRPC uses a 5-byte prefix: 1 byte compressed flag + 4 byte length - if (rawPayload.length < 5) { - return { - decoded_fields: [ - { - path: "error", - wire_type: "grpc", - value: "frame too short", - inferred: false, - }, - ], - truncated: false, - }; - } - const compressed = rawPayload[0] !== 0; - const length = - ((rawPayload[1] ?? 0) << 24) | - ((rawPayload[2] ?? 0) << 16) | - ((rawPayload[3] ?? 0) << 8) | - (rawPayload[4] ?? 0); - return { - decoded_fields: [ - { - path: "compressed", - wire_type: "grpc", - value: compressed, - inferred: false, - }, - { - path: "message_length", - wire_type: "grpc", - value: length, - inferred: false, - }, - ], - truncated: false, - }; -} - -/** Infer schema hypotheses from a set of decoded messages. */ -export function inferMessageSchema( - messages: readonly ProtocolMessage[], -): SchemaHypothesis[] { - const fieldCounts = new Map }>(); - for (const msg of messages) { - for (const field of msg.decoded_fields) { - const existing = fieldCounts.get(field.path); - if (existing) { - existing.count++; - if (field.wire_type) existing.types.add(field.wire_type); - } else { - fieldCounts.set(field.path, { - count: 1, - types: new Set(field.wire_type ?? "unknown"), - }); - } - } - } - const hypotheses: SchemaHypothesis[] = []; - for (const [field_name, data] of fieldCounts) { - hypotheses.push({ - field_name, - inferred_type: [...data.types].join("|"), - confidence: Math.min(1, data.count / messages.length || 0), - evidence_count: data.count, - }); - } - return hypotheses; -} diff --git a/src/domain/reconstructionCoverage.test.ts b/src/domain/reconstructionCoverage.test.ts index 01b0d329f..4c0b41831 100644 --- a/src/domain/reconstructionCoverage.test.ts +++ b/src/domain/reconstructionCoverage.test.ts @@ -7,6 +7,7 @@ import { } from "./reconstructionCoverage.fixture.js"; import { createReconstructionCoverageData, + createReconstructionVerifierContract, evaluateReconstructionClosure, } from "./reconstructionCoverage.js"; @@ -159,6 +160,168 @@ describe("reconstruction coverage closure", () => { }); describe("reconstruction coverage verifier observations", () => { + it("uses one contract and result for each claim covered by a shared verifier", () => { + const workspace = completeReconstructionCoverageData(); + const contract = workspace.verifier_contracts[0]; + const result = workspace.verifier_results[0]; + const claim = workspace.claims[0]; + const boundary = workspace.boundaries[0]; + if ( + contract === undefined || + result === undefined || + claim === undefined || + boundary === undefined + ) + throw new Error("Expected complete reconstruction coverage fixture"); + const secondClaimId = "claim.cli.version"; + const sharedContract = createReconstructionVerifierContract({ + verifier_id: contract.verifier_id, + claim_ids: [...contract.claim_ids, secondClaimId], + dimensions: contract.dimensions, + authority: contract.authority, + max_age_ms: contract.max_age_ms, + minimum_repeats: contract.minimum_repeats, + normalization_sha256: contract.normalization_sha256, + normalization_removes_dimensions: + contract.normalization_removes_dimensions, + }); + const sharedResult = { + ...result, + contract_sha256: sharedContract.contract_sha256, + covered_claim_ids: [...result.covered_claim_ids, secondClaimId], + }; + const shared = createReconstructionCoverageData({ + ...workspace, + claims: [ + ...workspace.claims, + { ...claim, claim_id: secondClaimId, title: "Version output matches" }, + ], + verifier_contracts: [sharedContract], + verifier_results: [sharedResult], + boundaries: [ + { + ...boundary, + required_claim_ids: [...boundary.required_claim_ids, secondClaimId], + }, + ], + }); + + expect( + evaluateReconstructionClosure( + shared, + boundary.boundary_id, + RECONSTRUCTION_COVERAGE_NOW, + ), + ).toMatchObject({ + status: "ready", + summary: { required_claims: 2, reasons: 0 }, + }); + }); +}); + +describe("reconstruction coverage verifier selection", () => { + it("keeps ambiguous verifier contracts and missing results actionable", () => { + const workspace = completeReconstructionCoverageData(); + const contract = workspace.verifier_contracts[0]; + const result = workspace.verifier_results[0]; + const boundary = workspace.boundaries[0]; + if ( + contract === undefined || + result === undefined || + boundary === undefined + ) + throw new Error("Expected complete reconstruction coverage fixture"); + const alternateContract = createReconstructionVerifierContract({ + verifier_id: "verify.cli.help.alternate", + claim_ids: contract.claim_ids, + dimensions: contract.dimensions, + authority: contract.authority, + max_age_ms: contract.max_age_ms, + minimum_repeats: contract.minimum_repeats, + normalization_sha256: contract.normalization_sha256, + normalization_removes_dimensions: + contract.normalization_removes_dimensions, + }); + const ambiguous = createReconstructionCoverageData({ + ...workspace, + verifier_contracts: [...workspace.verifier_contracts, alternateContract], + verifier_results: [ + ...workspace.verifier_results, + { + ...result, + verifier_id: alternateContract.verifier_id, + contract_sha256: alternateContract.contract_sha256, + }, + ], + }); + expect( + evaluateReconstructionClosure( + ambiguous, + boundary.boundary_id, + RECONSTRUCTION_COVERAGE_NOW, + ).reasons, + ).toEqual([ + expect.objectContaining({ + code: "verifier-duplicate", + subject_id: "claim.cli.help", + }), + ]); + + const missing = createReconstructionCoverageData({ + ...workspace, + verifier_results: [], + }); + expect( + evaluateReconstructionClosure( + missing, + boundary.boundary_id, + RECONSTRUCTION_COVERAGE_NOW, + ).reasons, + ).toEqual([ + expect.objectContaining({ + code: "verifier-result-missing", + subject_id: "claim.cli.help", + }), + ]); + }); +}); + +describe("reconstruction coverage verifier result ordering", () => { + it("preserves the first input result when verifier timestamps tie", () => { + const workspace = completeReconstructionCoverageData(); + const result = workspace.verifier_results[0]; + const boundary = workspace.boundaries[0]; + if (result === undefined || boundary === undefined) + throw new Error("Expected complete reconstruction coverage fixture"); + const failed = { ...result, status: "fail" as const }; + const firstPass = createReconstructionCoverageData({ + ...workspace, + verifier_results: [result, failed], + }); + const firstFail = createReconstructionCoverageData({ + ...workspace, + verifier_results: [failed, result], + }); + + expect( + evaluateReconstructionClosure( + firstPass, + boundary.boundary_id, + RECONSTRUCTION_COVERAGE_NOW, + ).status, + ).toBe("ready"); + expect( + evaluateReconstructionClosure( + firstFail, + boundary.boundary_id, + RECONSTRUCTION_COVERAGE_NOW, + ), + ).toMatchObject({ + status: "failed", + reasons: [expect.objectContaining({ code: "verifier-failed" })], + }); + }); + it("orders offset-bearing verifier timestamps chronologically", () => { const workspace = completeReconstructionCoverageData(); const result = workspace.verifier_results[0]; diff --git a/src/domain/reconstructionCoverageEvaluation.ts b/src/domain/reconstructionCoverageEvaluation.ts index ceb38c0cf..ea82e5636 100644 --- a/src/domain/reconstructionCoverageEvaluation.ts +++ b/src/domain/reconstructionCoverageEvaluation.ts @@ -140,6 +140,7 @@ export const evaluateReconstructionClaims = ( context: ReconstructionEvaluationContext, ): void => { const claims = indexUnique(context.coverage.claims, "claim_id"); + const indexes = createVerifierIndexes(context.coverage); for (const claimId of context.boundary.required_claim_ids) { const claim = claims.get(claimId); if (claim === undefined) { @@ -151,9 +152,7 @@ export const evaluateReconstructionClaims = ( ); continue; } - const contracts = context.coverage.verifier_contracts.filter( - ({ claim_ids: ids }) => ids.includes(claimId), - ); + const contracts = indexes.contractsByClaim.get(claimId) ?? []; if (contracts.length === 0) { addReason( context, @@ -174,22 +173,94 @@ export const evaluateReconstructionClaims = ( } const contract = contracts[0]; if (contract !== undefined) - evaluateVerifierResult(context, claim, contract); + evaluateVerifierResult(context, claim, contract, indexes); } }; +interface VerifierIndexes { + readonly contractsByClaim: ReadonlyMap< + string, + readonly ReconstructionVerifierContract[] + >; + readonly latestResultsByVerifier: ReadonlyMap< + string, + ReconstructionCoverageData["verifier_results"][number] + >; + readonly artifactDigests: ReadonlySet; + readonly ownerDigests: ReadonlySet; + readonly surfaces: ReadonlyMap< + string, + ReconstructionCoverageData["surfaces"][number] + >; + readonly artifacts: ReadonlyMap< + string, + ReconstructionCoverageData["artifacts"][number] + >; + readonly ownersBySurface: ReadonlyMap< + string, + readonly ReconstructionCoverageData["owners"][number][] + >; +} + +const createVerifierIndexes = ( + coverage: ReconstructionCoverageData, +): VerifierIndexes => { + const contractsByClaim = new Map(); + for (const contract of coverage.verifier_contracts) + for (const claimId of new Set(contract.claim_ids)) { + const contracts = contractsByClaim.get(claimId) ?? []; + contracts.push(contract); + contractsByClaim.set(claimId, contracts); + } + + const latestResultsByVerifier = new Map< + string, + ReconstructionCoverageData["verifier_results"][number] + >(); + for (const result of coverage.verifier_results) { + const latest = latestResultsByVerifier.get(result.verifier_id); + // Stable descending sort used to choose the first input record on ties. + if ( + latest === undefined || + Date.parse(result.observed_at) > Date.parse(latest.observed_at) + ) + latestResultsByVerifier.set(result.verifier_id, result); + } + + const ownersBySurface = new Map< + string, + ReconstructionCoverageData["owners"][number][] + >(); + for (const owner of coverage.owners) { + const owners = ownersBySurface.get(owner.surface_id) ?? []; + owners.push(owner); + ownersBySurface.set(owner.surface_id, owners); + } + + return { + contractsByClaim, + latestResultsByVerifier, + artifactDigests: new Set( + coverage.artifacts.map(({ artifact_sha256: value }) => value), + ), + ownerDigests: new Set( + coverage.owners.flatMap(({ ownership }) => + ownership.disposition === "implemented" ? [ownership.owner_sha256] : [], + ), + ), + surfaces: indexUnique(coverage.surfaces, "surface_id"), + artifacts: indexUnique(coverage.artifacts, "artifact_id"), + ownersBySurface, + }; +}; + const evaluateVerifierResult = ( context: ReconstructionEvaluationContext, claim: ReconstructionCoverageData["claims"][number], contract: ReconstructionVerifierContract, + indexes: VerifierIndexes, ): void => { - const results = context.coverage.verifier_results - .filter(({ verifier_id: id }) => id === contract.verifier_id) - .sort( - (left, right) => - Date.parse(right.observed_at) - Date.parse(left.observed_at), - ); - const result = results[0]; + const result = indexes.latestResultsByVerifier.get(contract.verifier_id); if (result === undefined) { addReason( context, @@ -200,7 +271,7 @@ const evaluateVerifierResult = ( return; } addEvidence(context, result.evidence_ids); - if (!verifierResultIsCompatible(context, claim, contract, result)) { + if (!verifierResultIsCompatible(claim, contract, result, indexes)) { addReason( context, "verifier-result-incompatible", @@ -239,40 +310,23 @@ const evaluateVerifierResult = ( }; const verifierResultIsCompatible = ( - context: ReconstructionEvaluationContext, claim: ReconstructionCoverageData["claims"][number], contract: ReconstructionVerifierContract, result: ReconstructionCoverageData["verifier_results"][number], + indexes: VerifierIndexes, ): boolean => { - const artifactDigests = new Set( - context.coverage.artifacts.map(({ artifact_sha256: value }) => value), - ); - const ownerDigests = new Set( - context.coverage.owners.flatMap(({ ownership }) => - ownership.disposition === "implemented" ? [ownership.owner_sha256] : [], - ), - ); - const surfaces = new Map( - context.coverage.surfaces.map((surface) => [surface.surface_id, surface]), - ); - const artifacts = new Map( - context.coverage.artifacts.map((artifact) => [ - artifact.artifact_id, - artifact, - ]), - ); const expectedArtifactDigests = claim.surface_ids.flatMap((surfaceId) => { - const surface = surfaces.get(surfaceId); + const surface = indexes.surfaces.get(surfaceId); const artifact = - surface === undefined ? undefined : artifacts.get(surface.artifact_id); + surface === undefined + ? undefined + : indexes.artifacts.get(surface.artifact_id); return artifact === undefined ? [] : [artifact.artifact_sha256]; }); - const expectedOwnerDigests = context.coverage.owners.flatMap( - ({ surface_id: surfaceId, ownership }) => - claim.surface_ids.includes(surfaceId) && - ownership.disposition === "implemented" - ? [ownership.owner_sha256] - : [], + const expectedOwnerDigests = claim.surface_ids.flatMap((surfaceId) => + (indexes.ownersBySurface.get(surfaceId) ?? []).flatMap(({ ownership }) => + ownership.disposition === "implemented" ? [ownership.owner_sha256] : [], + ), ); return ( result.contract_sha256 === contract.contract_sha256 && @@ -290,11 +344,13 @@ const verifierResultIsCompatible = ( result.covered_dimensions.every((item) => contract.dimensions.includes(item), ) && - result.artifact_sha256s.every((item) => artifactDigests.has(item)) && + result.artifact_sha256s.every((item) => + indexes.artifactDigests.has(item), + ) && expectedArtifactDigests.every((item) => result.artifact_sha256s.includes(item), ) && - result.owner_sha256s.every((item) => ownerDigests.has(item)) && + result.owner_sha256s.every((item) => indexes.ownerDigests.has(item)) && expectedOwnerDigests.every((item) => result.owner_sha256s.includes(item)) && result.repeats >= contract.minimum_repeats && claim.required_authority === contract.authority diff --git a/src/domain/reconstructionVerification.test.ts b/src/domain/reconstructionVerification.test.ts index a2a8573d6..2de3e7717 100644 --- a/src/domain/reconstructionVerification.test.ts +++ b/src/domain/reconstructionVerification.test.ts @@ -7,7 +7,7 @@ import { verifyReconstruction, } from "./reconstructionVerification.js"; import type { JsonValue } from "./jsonValue.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCapture.fixture.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./process/processCapture.fixture.js"; import { reconstructionSpecificationSchema } from "./reconstructionVerificationSchemas.js"; import { ARTIFACT_COMPARISON_EXAMPLE } from "../contracts/artifactComparisonExample.js"; import { createResidualUnknown } from "./residualUnknown.js"; @@ -23,6 +23,7 @@ const source = ( digit: string, authority: "controlled-replay" | "shipped-artifact", confidence: "observed" | "derived" = "observed", + captureResult: JsonValue = EMPTY_PROCESS_CAPTURE_EXAMPLE, ): Evidence => createEvidence( { @@ -41,16 +42,26 @@ const source = ( ? "capture_process_scenario" : "inventory_artifact", parameters: {}, - result: - authority === "controlled-replay" - ? EMPTY_PROCESS_CAPTURE_EXAMPLE - : { digit }, + result: authority === "controlled-replay" ? captureResult : { digit }, confidence, authority, environment: authority === "controlled-replay" ? environment : null, }, ); +const legacyV3Capture = () => { + const { + selected_executable_sha256: _selectedDigest, + executable_identity: _executableIdentity, + executable_sha256, + ...legacyManifest + } = EMPTY_PROCESS_CAPTURE_EXAMPLE.manifest; + return { + ...EMPTY_PROCESS_CAPTURE_EXAMPLE, + manifest: { ...legacyManifest, executable_sha256 }, + }; +}; + const processResult = ( terminal: "unchanged" | "changed" | "unknown" = "unchanged", ): JsonValue => ({ @@ -275,7 +286,48 @@ describe("reconstruction verification", () => { ]), ); }); +}); +describe("saved v3 reconstruction capture compatibility", () => { + it("verifies saved v3 process captures and rejects a malformed legacy digest", () => { + const legacy = legacyV3Capture(); + const left = source("1", "controlled-replay", "observed", legacy); + const right = source("2", "controlled-replay"); + const comparison = processComparison(left, right, processResult()); + const bundle = createEvidenceBundle([comparison, left, right]); + + expect( + verifyReconstruction(behavioralSpec(comparison), bundle), + ).toMatchObject({ + status: "pass", + summary: { total: 1, passed: 1, failed: 0, unknown: 0 }, + }); + + const malformedLegacy = { + ...legacy, + manifest: { ...legacy.manifest, executable_sha256: null }, + }; + const malformedLeft = source( + "3", + "controlled-replay", + "observed", + malformedLegacy, + ); + const malformedComparison = processComparison( + malformedLeft, + right, + processResult(), + ); + expect(() => + verifyReconstruction( + behavioralSpec(malformedComparison), + createEvidenceBundle([malformedComparison, malformedLeft, right]), + ), + ).toThrow(); + }); +}); + +describe("reconstruction verification outcomes", () => { it("fails an authoritative observed disagreement", () => { const left = source("1", "controlled-replay"); const right = source("2", "controlled-replay"); diff --git a/src/domain/reconstructionVerification.ts b/src/domain/reconstructionVerification.ts index 1900c93be..3de1d8509 100644 --- a/src/domain/reconstructionVerification.ts +++ b/src/domain/reconstructionVerification.ts @@ -12,14 +12,14 @@ import { functionComparisonResultSchema } from "./functionComparison.js"; import { PROCESS_PROVIDER, isProcessEvidenceProvider, -} from "./processEvidenceProvider.js"; +} from "./process/processEvidenceProvider.js"; import { functionDossierSchema } from "./hopperValues.js"; import { deriveProcessComparisonStatus, PROCESS_COMPARISON_DIMENSIONS, processCaptureComparisonSchema, - processCaptureSchema, -} from "./processCapture.js"; + parseProcessCapture, +} from "./process/processCapture.js"; import { reconstructionClaimResultSchema, reconstructionSpecificationSchema, @@ -199,7 +199,7 @@ const validateSourceKinds = ( ); for (const source of sources) { if (claim.kind === "behavioral") - processCaptureSchema.parse(source.normalized_result); + parseProcessCapture(source.normalized_result); else if (claim.kind === "structural-function") functionDossierSchema.parse(source.normalized_result); else artifactInventoryResultSchema.parse(source.normalized_result); diff --git a/src/domain/reconstructionVerificationSchemas.ts b/src/domain/reconstructionVerificationSchemas.ts index 023cd3b11..4ce88ba7a 100644 --- a/src/domain/reconstructionVerificationSchemas.ts +++ b/src/domain/reconstructionVerificationSchemas.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { digestSchema } from "./../domain/digests.js"; import { prefixedDigestSchema } from "./../domain/digests.js"; -import { PROCESS_COMPARISON_DIMENSIONS } from "./processComparison.js"; +import { PROCESS_COMPARISON_DIMENSIONS } from "./process/processComparison.js"; const evidenceIdSchema = prefixedDigestSchema("ev"); const unknownIdSchema = prefixedDigestSchema("unk"); diff --git a/src/domain/residualUnknown.ts b/src/domain/residualUnknown.ts index 396f72dcb..7695c340c 100644 --- a/src/domain/residualUnknown.ts +++ b/src/domain/residualUnknown.ts @@ -167,12 +167,13 @@ export const recordUnknownInputSchema = residualUnknownObjectSchema .extend({ supporting_evidence_ids: z.array(evidenceIdSchema).default([]), contradicting_evidence_ids: z.array(evidenceIdSchema).default([]), - }); + }) + .strict(); export type RecordUnknownInput = z.infer; /** Explicit optimistic-concurrency update command for one unknown. */ -export const updateUnknownInputSchema = z.object({ +export const updateUnknownInputSchema = z.strictObject({ unknown_id: unknownIdSchema, expected_revision: z.number().int().min(1), status: residualUnknownObjectSchema.shape.status, diff --git a/src/domain/runtimeIdentification.ts b/src/domain/runtimeIdentification.ts deleted file mode 100644 index 8a58a096d..000000000 --- a/src/domain/runtimeIdentification.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { createHash } from "node:crypto"; - -import canonicalize from "canonicalize"; -import { z } from "zod"; - -import { parseArtifactInventoryEvidence } from "./artifactInventoryEvidence.js"; -import { evidenceSchema } from "./evidence.js"; -import { digestSchema } from "./../domain/digests.js"; -import { prefixedDigestSchema } from "./../domain/digests.js"; - -const evidenceIdSchema = prefixedDigestSchema("ev"); -const runtimeFamilySchema = z.enum([ - "android", - "apple", - "javascript", - "jvm", - "native", - "webassembly", -]); -const observationSchema = z.strictObject({ - path: z.string().min(1), - artifact_id: prefixedDigestSchema("art"), - sha256: digestSchema, - format: z.string().min(1), -}); -const runtimeObservationShape = { - family: runtimeFamilySchema, - observations: z.array(observationSchema), -}; -const identifiedRuntimeSchema = z.discriminatedUnion("inspection", [ - z.strictObject({ - ...runtimeObservationShape, - inspection: z.literal("available"), - provider_id: z.string().min(1), - reason: z.null(), - }), - z.strictObject({ - ...runtimeObservationShape, - inspection: z.literal("provider-missing"), - provider_id: z.null(), - reason: z.string().min(1), - }), - z.strictObject({ - ...runtimeObservationShape, - inspection: z.literal("provider-selection-required"), - provider_id: z.null(), - reason: z.string().min(1), - }), -]); - -/** Authenticated artifact inventory pages to classify by runtime family. */ -const runtimeIdentificationInputSchema = z.strictObject({ - inventory_evidence: z.array(evidenceSchema).min(1), -}); - -/** Provider-neutral runtime identification with explicit tooling availability. */ -export const runtimeIdentificationResultSchema = z.strictObject({ - identification_id: prefixedDigestSchema("rid"), - root_sha256: digestSchema, - root_format: z.string().min(1), - source_evidence_ids: z.array(evidenceIdSchema).min(1), - runtimes: z.array(identifiedRuntimeSchema), - coverage: z.strictObject({ - status: z.enum(["complete-within-inventory", "partial"]), - inventory_complete: z.boolean(), - }), - limitations: z.array(z.string().min(1)), -}); - -export type RuntimeIdentificationInput = z.input< - typeof runtimeIdentificationInputSchema ->; -export type RuntimeIdentificationResult = z.infer< - typeof runtimeIdentificationResultSchema ->; -type RuntimeFamily = z.infer; -type Observation = z.infer; -type RuntimeProvider = - | { - readonly inspection: "available"; - readonly id: string; - readonly reason: null; - } - | { - readonly inspection: "provider-missing" | "provider-selection-required"; - readonly id: null; - readonly reason: string; - }; - -const PROVIDERS: Readonly> = { - android: { - inspection: "available", - id: "rea-android-application", - reason: null, - }, - apple: { inspection: "available", id: "rea-apple-application", reason: null }, - javascript: { - inspection: "available", - id: "rea-javascript-application", - reason: null, - }, - jvm: { - inspection: "provider-missing", - id: null, - reason: "Install or configure a JVM bytecode-analysis provider.", - }, - native: { - inspection: "provider-selection-required", - id: null, - reason: - "Select an available native deep-analysis provider for this target and host.", - }, - webassembly: { - inspection: "provider-missing", - id: null, - reason: "Install or configure a WebAssembly semantic-analysis provider.", - }, -}; - -/** Identify runtime families without executing or semantically decoding target bytes. */ -export const identifyRuntimes = ( - input: RuntimeIdentificationInput, -): RuntimeIdentificationResult => { - const parsed = runtimeIdentificationInputSchema.parse(input); - const { evidence, inventory } = parseArtifactInventoryEvidence( - parsed.inventory_evidence, - ); - const nodes = new Map( - inventory.nodes.map((node) => [node.artifact_id, node]), - ); - const grouped = new Map(); - const add = (family: RuntimeFamily, observation: Observation): void => { - const observations = grouped.get(family) ?? []; - observations.push(observation); - grouped.set(family, observations); - }; - for (const occurrence of inventory.occurrences) { - if (occurrence.artifact_id === null) continue; - const node = nodes.get(occurrence.artifact_id); - if (node === undefined) - throw new TypeError("Runtime observation has no artifact node"); - const observation = { - path: occurrence.logical_path, - artifact_id: node.artifact_id, - sha256: node.sha256, - format: node.format, - }; - for (const family of familiesFor(node.format, occurrence.logical_path)) - add(family, observation); - } - const rootObservation = inventory.nodes.find( - ({ sha256 }) => sha256 === inventory.manifest.root_sha256, - ); - if (rootObservation !== undefined) - for (const family of familiesFor(inventory.manifest.root_format, ".")) - add(family, { - path: ".", - artifact_id: rootObservation.artifact_id, - sha256: rootObservation.sha256, - format: rootObservation.format, - }); - const runtimes = [...grouped.entries()] - .sort(([left], [right]) => compare(left, right)) - .map(([family, values]) => { - const unique = deduplicate(values); - const provider = providerFor(family, inventory.manifest.root_format); - return { - family, - ...runtimeInspection(provider), - observations: unique, - }; - }); - const limitations = [ - ...(!inventory.complete - ? [ - "Source inventory pages are incomplete; absent runtimes remain unknown.", - ] - : []), - "Runtime families are identified from exact artifact formats and paths; semantic claims require the named provider.", - ]; - const withoutId = { - root_sha256: inventory.manifest.root_sha256, - root_format: inventory.manifest.root_format, - source_evidence_ids: evidence - .map(({ evidence_id: id }) => id) - .sort(compare), - runtimes, - coverage: { - status: inventory.complete - ? ("complete-within-inventory" as const) - : ("partial" as const), - inventory_complete: inventory.complete, - }, - limitations, - }; - return runtimeIdentificationResultSchema.parse({ - ...withoutId, - identification_id: `rid_${digest(withoutId)}`, - }); -}; - -const providerFor = ( - family: RuntimeFamily, - rootFormat: string, -): RuntimeProvider => - family === "android" && rootFormat !== "apk" - ? { - inspection: "provider-selection-required", - id: null, - reason: - "Extract or select an APK root before using the Android application provider.", - } - : PROVIDERS[family]; - -const runtimeInspection = (provider: RuntimeProvider) => - provider.inspection === "available" - ? { - inspection: provider.inspection, - provider_id: provider.id, - reason: null, - } - : { - inspection: provider.inspection, - provider_id: null, - reason: provider.reason, - }; - -const familiesFor = (format: string, path: string): RuntimeFamily[] => { - const families = new Set(); - if ( - format === "apk" || - format === "dex" || - /(?:^|\/)[^/]+\.dex$/iu.test(path) || - /(?:^|\/)AndroidManifest\.xml$/u.test(path) - ) - families.add("android"); - if (format === "ipa") families.add("apple"); - if (format === "javascript-bundle") families.add("javascript"); - if (format === "jvm-class" || /(?:^|\/)[^/]+\.class$/iu.test(path)) - families.add("jvm"); - if ( - ["elf", "mach-o", "mach-o-universal", "pe", "dos-mz", "dos-com"].includes( - format, - ) - ) - families.add("native"); - if (format === "webassembly" || /(?:^|\/)[^/]+\.wasm$/iu.test(path)) - families.add("webassembly"); - return [...families]; -}; - -const deduplicate = (values: readonly Observation[]): Observation[] => - [ - ...new Map( - values.map((value) => [`${value.path}\0${value.artifact_id}`, value]), - ).values(), - ].sort((left, right) => compare(left.path, right.path)); -const compare = (left: string, right: string): number => - left < right ? -1 : left > right ? 1 : 0; -const digest = (value: unknown): string => { - const encoded = canonicalize(value); - if (encoded === undefined) - throw new TypeError("Runtime identification is not canonical JSON"); - return createHash("sha256").update(encoded).digest("hex"); -}; diff --git a/src/domain/staticRuntimeCorrelation.ts b/src/domain/staticRuntimeCorrelation.ts index 69d36b855..d5558ad27 100644 --- a/src/domain/staticRuntimeCorrelation.ts +++ b/src/domain/staticRuntimeCorrelation.ts @@ -10,13 +10,13 @@ import { functionComparisonResultSchema } from "./functionComparison.js"; import { PROCESS_PROVIDER, isProcessEvidenceProvider, -} from "./processEvidenceProvider.js"; +} from "./process/processEvidenceProvider.js"; import type { JsonValue } from "./jsonValue.js"; import { comparisonStatusSchema, processCaptureComparisonSchema, -} from "./processCapture.js"; -import { PROCESS_COMPARISON_DIMENSIONS } from "./processComparison.js"; +} from "./process/processCapture.js"; +import { PROCESS_COMPARISON_DIMENSIONS } from "./process/processComparison.js"; import { prefixedDigestSchema } from "./../domain/digests.js"; const evidenceIdSchema = prefixedDigestSchema("ev"); diff --git a/src/domain/unicodeCodePointOrder.ts b/src/domain/unicodeCodePointOrder.ts index 42acf2b06..163039364 100644 --- a/src/domain/unicodeCodePointOrder.ts +++ b/src/domain/unicodeCodePointOrder.ts @@ -3,12 +3,16 @@ export const compareUnicodeCodePoints = ( left: string, right: string, ): number => { - const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0); - const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0); - const length = Math.min(leftPoints.length, rightPoints.length); - for (let index = 0; index < length; index += 1) { - const difference = (leftPoints[index] ?? 0) - (rightPoints[index] ?? 0); + let leftIndex = 0; + let rightIndex = 0; + while (leftIndex < left.length && rightIndex < right.length) { + const leftPoint = left.codePointAt(leftIndex) ?? 0; + const rightPoint = right.codePointAt(rightIndex) ?? 0; + const difference = leftPoint - rightPoint; if (difference !== 0) return difference; + leftIndex += leftPoint > 0xffff ? 2 : 1; + rightIndex += rightPoint > 0xffff ? 2 : 1; } - return leftPoints.length - rightPoints.length; + if (leftIndex === left.length && rightIndex === right.length) return 0; + return leftIndex === left.length ? -1 : 1; }; diff --git a/src/domain/webCaptureDiff.ts b/src/domain/webCaptureDiff.ts index ea8aa0420..d92c2848b 100644 --- a/src/domain/webCaptureDiff.ts +++ b/src/domain/webCaptureDiff.ts @@ -249,6 +249,13 @@ const networkMap = ( status: request.status, mime_type: request.mime_type, encoded_data_length: request.encoded_data_length, + redirects: (request.redirects ?? []).map( + ({ + request_timestamp: _requestTimestamp, + redirect_event_timestamp: _redirectEventTimestamp, + ...redirect + }) => redirect, + ), initiator: request.initiator, body_shapes: request.body_shapes, }); diff --git a/src/domain/webNetworkCapture.ts b/src/domain/webNetworkCapture.ts new file mode 100644 index 000000000..bda9334bb --- /dev/null +++ b/src/domain/webNetworkCapture.ts @@ -0,0 +1,141 @@ +import { z } from "zod"; +import { jsonValueSchema } from "./jsonValue.js"; + +/** Complete historical evidence budgets; exceeding a budget returns no partial capture. */ +export const WEB_NETWORK_CAPTURE_LIMITS = { + inputBytes: 32 * 1024 * 1024, + outputBytes: 96 * 1024 * 1024, + timeoutMs: 30_000, + depth: 64, +} as const; + +/** Inspect an explicit retained capture without contacting its recorded destinations. */ +export const inspectWebNetworkCaptureInputSchema = z.strictObject({ + capture_path: z + .string() + .min(1) + .describe("Absolute local HAR or mitmproxy capture path."), + format: z.enum(["har", "mitmproxy"]), + record_ordinals: z + .array(z.number().int().nonnegative()) + .min(1) + .optional() + .describe( + "Zero-based producer record ordinals, in caller-selected order. Omit to inspect every record.", + ), + sensitive_values: z + .array(z.string().min(1)) + .default([]) + .describe( + "Literal text excluded from capture payloads, byte/numeric projections, producer coordinates, artifact paths/digests and diagnostic text. Declaration strings are never persisted in Evidence parameters. Canonical contract keys, enums and provider identities remain intact.", + ), +}); + +const pointerSchema = z + .string() + .describe("RFC 6901 pointer in the producer's reported object."); +const redactionSchema = z.union([ + z.strictObject({ + pointer: pointerSchema, + reason: z.enum(["transport-credential", "explicit-sensitive-value"]), + }), + z.strictObject({ + pointer: pointerSchema, + reason: z.literal("explicit-sensitive-value"), + scope: z + .literal("property-name") + .describe( + "An omitted property name; pointer identifies its actual parent, not an invented child coordinate.", + ), + }), +]); +const binaryBase = { + pointer: pointerSchema, + representation: z.enum(["producer-bytes", "har-base64-content"]), +}; +const binarySchema = z.discriminatedUnion("state", [ + z.strictObject({ + ...binaryBase, + state: z.literal("retained"), + content_base64: z.string(), + bytes: z.number().int().nonnegative(), + sha256: z.string().regex(/^[0-9a-f]{64}$/), + }), + z.strictObject({ + ...binaryBase, + state: z.literal("redacted"), + content_base64: z.null(), + bytes: z.null(), + sha256: z.null(), + }), +]); +const numberSchema = z.strictObject({ + pointer: pointerSchema, + producer_type: z.enum(["json-number", "integer", "float"]), + literal: z.string(), +}); + +/** Preserve producer representations with independent sidecars rather than invented browser IDs. */ +export const webNetworkCaptureRecordSchema = z.strictObject({ + ordinal: z.number().int().nonnegative(), + location: z.discriminatedUnion("kind", [ + z.strictObject({ + kind: z.literal("unknown"), + reason: z.literal("explicit-sensitive-value"), + }), + z.strictObject({ kind: z.literal("json-pointer"), pointer: pointerSchema }), + z.strictObject({ + kind: z.literal("byte-range"), + offset: z.number().int().nonnegative(), + bytes: z.number().int().positive(), + }), + ]), + reported: jsonValueSchema, + binary_fields: z.array(binarySchema), + numeric_literals: z.array(numberSchema), + redactions: z.array(redactionSchema), + limitations: z.array(z.string()), +}); + +/** Historical capture inspection has file identity, producer provenance and explicit unknowns. */ +export const webNetworkCaptureSchema = z.strictObject({ + artifact: z.strictObject({ + path: z + .string() + .describe( + "Selected artifact path; empty when explicitly excluded from Evidence.", + ), + sha256: z + .string() + .regex(/^[0-9a-f]{64}$/) + .nullable() + .describe( + "Observed artifact SHA-256; null only when explicitly excluded by a sensitive declaration.", + ), + bytes: z.number().int().nonnegative(), + }), + format: z.enum(["har", "mitmproxy"]), + decoder: z.strictObject({ + id: z.string().min(1), + name: z.string().min(1), + version: z.string().min(1), + }), + container: z.strictObject({ + reported: jsonValueSchema, + numeric_literals: z.array(numberSchema), + redactions: z.array(redactionSchema), + records_pointer: pointerSchema.nullable(), + }), + total_records: z.number().int().nonnegative(), + records: z.array(webNetworkCaptureRecordSchema), + runtime_attribution: z.literal("unknown"), + limitations: z.array(z.string()), +}); + +export type InspectWebNetworkCaptureInput = z.output< + typeof inspectWebNetworkCaptureInputSchema +>; +export type WebNetworkCapture = z.output; +export type WebNetworkCaptureRecord = z.output< + typeof webNetworkCaptureRecordSchema +>; diff --git a/src/domain/webNetworkCaptureCoordinates.ts b/src/domain/webNetworkCaptureCoordinates.ts new file mode 100644 index 000000000..6cba46cd9 --- /dev/null +++ b/src/domain/webNetworkCaptureCoordinates.ts @@ -0,0 +1,68 @@ +import { redactExplicitText } from "./explicitSensitiveValues.js"; +import type { WebNetworkCapture } from "./webNetworkCapture.js"; + +/** Exclude marked source coordinates without inventing a substitute record identity. */ +export const excludeCaptureCoordinates = ( + value: WebNetworkCapture, + sensitiveValues: readonly string[], +): WebNetworkCapture => { + if (sensitiveValues.length === 0) return value; + let excluded = false; + const sensitive = (pointer: string): boolean => + sensitiveValues.some((literal) => pointer.includes(literal)); + const sidecars = ( + items: readonly T[], + ): T[] => + items.filter((item) => { + if (!sensitive(item.pointer)) return true; + excluded = true; + return false; + }); + const records = value.records.map((record) => { + const location = + (record.location.kind === "json-pointer" && + sensitive(record.location.pointer)) || + (record.location.kind === "byte-range" && + (sensitive(String(record.location.offset)) || + sensitive(String(record.location.bytes)))) + ? { + kind: "unknown" as const, + reason: "explicit-sensitive-value" as const, + } + : record.location; + if (location !== record.location) excluded = true; + return { + ...record, + location, + binary_fields: sidecars(record.binary_fields), + numeric_literals: sidecars(record.numeric_literals), + redactions: sidecars(record.redactions), + }; + }); + const recordsPointer = value.container.records_pointer; + const records_pointer = + recordsPointer !== null && sensitive(recordsPointer) + ? null + : recordsPointer; + if (records_pointer !== recordsPointer) excluded = true; + const container = { + ...value.container, + records_pointer, + numeric_literals: sidecars(value.container.numeric_literals), + redactions: sidecars(value.container.redactions), + }; + return { + ...value, + container, + records, + limitations: excluded + ? [ + ...value.limitations, + redactExplicitText( + "Marked source coordinates and their sidecars are excluded; affected record locations remain unknown.", + sensitiveValues, + ), + ] + : value.limitations, + }; +}; diff --git a/src/dotnet/ManagedArtifactFailures.test.ts b/src/dotnet/ManagedArtifactFailures.test.ts index b80e14463..1647157b5 100644 --- a/src/dotnet/ManagedArtifactFailures.test.ts +++ b/src/dotnet/ManagedArtifactFailures.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "vitest"; import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js"; +import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js"; +import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInspector.js"; import { buildManagedPeFixture, buildNativePeFixture, @@ -43,4 +45,37 @@ describe("managed artifact failure classification", () => { expect.objectContaining({ code: "invalid-tables" }), ]); }); + + it("keeps malformed reference and attribute issues across inventory callers", () => { + const bytes = buildManagedPeFixture({ + references: [ + "System.Runtime", + "System.Runtime", + "UnityEngine.CoreModule", + ], + malformedAssemblyReferenceRows: [3], + malformedCustomAttributeRows: [1], + }); + const target = managedPeFixtureTarget(bytes); + const artifact = inspectManagedArtifactBytes(bytes, target); + const members = inspectManagedMembersBytes(bytes, target); + const boundaries = inspectManagedNativeBoundariesBytes(bytes, target); + const issueCodes = ["invalid-heap-index", "invalid-row"]; + + expect(artifact.references.map(({ name }) => name)).toEqual([ + "System.Runtime", + "System.Runtime", + ]); + expect(artifact.coverage.issues.map(({ code }) => code)).toEqual( + issueCodes, + ); + expect(members.coverage.issues.map(({ code }) => code)).toEqual(issueCodes); + expect(boundaries.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ code: "invalid-heap-index" }), + expect.objectContaining({ code: "invalid-row" }), + ], + }); + }); }); diff --git a/src/dotnet/ManagedArtifactInspector.test.ts b/src/dotnet/ManagedArtifactInspector.test.ts index e98e7aeaf..638dfd353 100644 --- a/src/dotnet/ManagedArtifactInspector.test.ts +++ b/src/dotnet/ManagedArtifactInspector.test.ts @@ -9,8 +9,66 @@ import { buildManagedPeFixture, managedPeFixtureTarget, } from "./ManagedPe.fixture.js"; +import { readManagedMetadataLayout } from "./ManagedMetadataLayout.js"; +import { readManagedPeLayout } from "./ManagedPeReader.js"; + +const metadataLayoutFor = (bytes: Buffer) => { + const pe = readManagedPeLayout(bytes); + if (pe.cli === null) throw new Error("fixture omitted CLI metadata"); + return readManagedMetadataLayout( + bytes, + pe.rvaToOffset(pe.cli.metadata.rva, pe.cli.metadata.size, "cli.metadata"), + pe.cli.metadata.size, + ); +}; describe("managed artifact inventory", () => { + it.each([ + { moduleRowCount: 0, assemblyRowCount: 1, scope: "metadata.Module" }, + { moduleRowCount: 2, assemblyRowCount: 1, scope: "metadata.Module" }, + { moduleRowCount: 1, assemblyRowCount: 2, scope: "metadata.Assembly" }, + ])("marks invalid identity table cardinality partial: %j", (options) => { + const bytes = buildManagedPeFixture(options); + const target = managedPeFixtureTarget(bytes); + const results = [ + inspectManagedArtifactBytes(bytes, target), + inspectManagedMembersBytes(bytes, target), + inspectManagedNativeBoundariesBytes(bytes, target), + ]; + + for (const result of results) { + expect(result.metadata.status).toBe("partial"); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + code: "invalid-row", + scope: options.scope, + }), + ], + }); + } + if (options.moduleRowCount === 0) expect(results[0]?.module).toBeNull(); + else expect(results[0]?.module?.name).toBe("Fixture.dll"); + }); + + it.each([ + { moduleRowCount: 1, assemblyRowCount: 0, targetFramework: null }, + { moduleRowCount: 1, assemblyRowCount: 1 }, + ])("keeps valid identity table cardinalities complete: %j", (options) => { + const bytes = buildManagedPeFixture(options); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.metadata.status).toBe("complete"); + expect(result.coverage).toMatchObject({ state: "complete", issues: [] }); + expect(result.assembly?.name ?? null).toBe( + options.assemblyRowCount === 0 ? null : "Fixture.Managed", + ); + }); + it("statically inspects PE files with more than 96 sections", () => { const fixture = buildManagedPeFixture(); const peOffset = fixture.readUInt32LE(0x3c); @@ -77,7 +135,133 @@ describe("managed artifact inventory", () => { ]); expect(result.coverage).toMatchObject({ state: "complete", issues: [] }); }); +}); +describe("managed artifact coded indexes", () => { + it("reports an invalid ManifestResource implementation while retaining the resource", () => { + const bytes = buildManagedPeFixture({ resourceImplementationRaw: 9 }); + const target = managedPeFixtureTarget(bytes); + const result = inspectManagedArtifactBytes(bytes, target); + const boundaries = inspectManagedNativeBoundariesBytes(bytes, target); + + expect(result.resources).toMatchObject([ + { + name: "Fixture.resources", + implementation_token: null, + embedded: false, + }, + ]); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + code: "invalid-row", + scope: "metadata.ManifestResource:0x28000001", + detail: expect.stringContaining( + "coded index 0x9 is invalid: coded index selects row 2 in table 35, which has 1 rows", + ), + }), + ], + }); + const layout = metadataLayoutFor(bytes); + expect(result.coverage.issues[0]?.offset).toBe( + (result.resources[0]?.row_offset ?? 0) + 8 + layout.stringIndexSize, + ); + expect(boundaries.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + scope: "metadata.ManifestResource:0x28000001", + }), + ], + }); + }); + + it("reports an invalid CustomAttribute constructor coded index", () => { + const bytes = buildManagedPeFixture({ customAttributeTypeRaw: 19 }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.attributes).toEqual([]); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + code: "invalid-row", + scope: "metadata.CustomAttribute:0x0c000001", + detail: expect.stringContaining( + "coded index 0x13 is invalid: coded index selects row 2 in table 10, which has 1 rows", + ), + }), + ], + }); + }); + + it("reports null for the required CustomAttribute constructor coded index", () => { + const bytes = buildManagedPeFixture({ customAttributeTypeRaw: 0 }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.attributes).toEqual([]); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + code: "invalid-row", + detail: + "CustomAttribute constructor coded index 0x0 is null, but Type must reference a MethodDef or MemberRef row", + }), + ], + }); + }); + + it("reports coded-index column offsets after widened string and coded-index columns", () => { + const references = Array.from( + { length: 2_500 }, + (_, index) => `Reference.${"r".repeat(24)}.${String(index)}`, + ); + const bytes = buildManagedPeFixture({ + references, + extendsRaw: 9, + customAttributeTypeRaw: 19, + }); + const layout = metadataLayoutFor(bytes); + const artifact = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + const members = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + const extendsIssue = members.coverage.issues.find((issue) => + issue.scope.startsWith("metadata.TypeDef:"), + ); + const attributeIssue = artifact.coverage.issues.find((issue) => + issue.scope.startsWith("metadata.CustomAttribute:"), + ); + const typeDef = members.types[0]; + const attributeTable = layout.table(12); + + expect(layout.stringIndexSize).toBe(4); + expect(layout.codedIndexSize("HasCustomAttribute")).toBe(4); + expect(typeDef).toBeDefined(); + expect(extendsIssue?.offset).toBe( + (typeDef?.row_offset ?? 0) + 4 + 2 * layout.stringIndexSize, + ); + expect(attributeTable).toBeDefined(); + expect(attributeIssue?.offset).toBe( + (attributeTable?.offset ?? 0) + + layout.codedIndexSize("HasCustomAttribute"), + ); + }); +}); + +describe("managed artifact reference inventory", () => { it("returns all references inline while classifying from the full inventory", () => { const bytes = buildManagedPeFixture({ references: ["System.Runtime", "UnityEngine.CoreModule"], @@ -93,6 +277,60 @@ describe("managed artifact inventory", () => { expect(result.coverage.issues).toEqual([]); }); + it("preserves duplicate AssemblyRef rows in metadata order", () => { + const bytes = buildManagedPeFixture({ + references: [ + "UnityEngine.CoreModule", + "UnityEngine.CoreModule", + "System.Runtime", + ], + }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.references.map(({ name }) => name)).toEqual([ + "UnityEngine.CoreModule", + "UnityEngine.CoreModule", + "System.Runtime", + ]); + expect(result.classification.runtime_family).toBe("unity-mono"); + }); +}); + +describe("managed fixture data layout", () => { + it("keeps ReadyToRun metadata and resources intact with large metadata", () => { + const resource = Buffer.alloc(2_048, 0x52); + const fieldSignature = Buffer.concat([ + Buffer.from([0x06]), + Buffer.alloc(8_000, 0x0f), + Buffer.from([0x08]), + ]); + const bytes = buildManagedPeFixture({ + fieldSignature, + readyToRun: true, + resourceData: resource, + }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.classification).toMatchObject({ + status: "managed", + implementation: "cil-and-ready-to-run", + }); + expect(result.resources[0]).toMatchObject({ + embedded: true, + data_length: resource.length, + data_sha256: createHash("sha256").update(resource).digest("hex"), + }); + expect(result.coverage).toMatchObject({ state: "complete", issues: [] }); + }); +}); + +describe("managed artifact metadata identity", () => { it("accepts CLI metadata GUIDs without RFC UUID version or variant bits", () => { const bytes = buildManagedPeFixture({ mvid: Buffer.from("3aebc60edc4a544b1f458b4ed40b33b1", "hex"), @@ -140,6 +378,55 @@ describe("managed artifact inventory", () => { }); }); +it("derives the string heap width after expanded AssemblyRef names", () => { + const references = Array.from( + { length: 5_000 }, + (_, index) => `Reference.${String(index).padStart(4, "0")}`, + ); + const bytes = buildManagedPeFixture({ references }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.references).toHaveLength(references.length); + expect(result.references.at(-1)?.name).toBe("Reference.4999"); + expect(result.coverage).toMatchObject({ state: "complete", issues: [] }); + + const malformedBytes = buildManagedPeFixture({ + references, + malformedAssemblyReferenceRows: [5_000], + }); + const malformedResult = inspectManagedArtifactBytes( + malformedBytes, + managedPeFixtureTarget(malformedBytes), + ); + expect(malformedResult.references).toHaveLength(4_999); + expect(malformedResult.coverage).toMatchObject({ + state: "partial", + issues: [expect.objectContaining({ code: "invalid-heap-index" })], + }); +}); + +it("derives ResolutionScope and Implementation widths at 16,384 AssemblyRefs", () => { + const references = Array.from( + { length: 16_384 }, + (_, index) => `Reference.${String(index).padStart(5, "0")}`, + ); + const bytes = buildManagedPeFixture({ references }); + const result = inspectManagedArtifactBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.references).toHaveLength(references.length); + expect(result.references.at(-1)?.name).toBe("Reference.16383"); + expect(result.resources).toMatchObject([ + { name: "Fixture.resources", embedded: true, data_length: 13 }, + ]); + expect(result.coverage).toMatchObject({ state: "complete", issues: [] }); +}); + it("preserves U+FEFF in references and fixed attribute strings", () => { const bytes = buildManagedPeFixture({ references: ["\uFEFFSystem.Runtime", "System.\uFEFFRuntime"], diff --git a/src/dotnet/ManagedCilDecoding.test.ts b/src/dotnet/ManagedCilDecoding.test.ts index d143d7491..c1ed21130 100644 --- a/src/dotnet/ManagedCilDecoding.test.ts +++ b/src/dotnet/ManagedCilDecoding.test.ts @@ -25,6 +25,48 @@ const managedBodyWithSections = (...sections: Buffer[]) => { .methods[0]?.body; }; +const managedBodyWithRegion = ({ + il, + flags = 0, + tryOffset = 0, + tryLength = 1, + handlerOffset, + handlerLength = 1, + extra = 0x0200_0001, +}: { + readonly il: Buffer; + readonly flags?: number; + readonly tryOffset?: number; + readonly tryLength?: number; + readonly handlerOffset: number; + readonly handlerLength?: number; + readonly extra?: number; +}) => { + const header = Buffer.alloc(12); + header.writeUInt16LE(0x300b, 0); + header.writeUInt16LE(8, 2); + header.writeUInt32LE(il.length, 4); + const section = Buffer.alloc(16); + section[0] = 0x01; + section[1] = section.length; + section.writeUInt16LE(flags, 4); + section.writeUInt16LE(tryOffset, 6); + section[8] = tryLength; + section.writeUInt16LE(handlerOffset, 9); + section[11] = handlerLength; + section.writeUInt32LE(extra, 12); + const bytes = buildManagedPeFixture({ + ilBody: Buffer.concat([ + header, + il, + Buffer.alloc((4 - (il.length % 4)) % 4), + section, + ]), + }); + return inspectManagedMembersBytes(bytes, managedPeFixtureTarget(bytes)) + .methods[0]?.body; +}; + describe("managed CIL decoding", () => { it("reads fat method header size from the full flags-and-size word", () => { const il = Buffer.from([ @@ -79,6 +121,60 @@ describe("managed CIL decoding", () => { }); }); + it("does not decode overlay bytes after the method's file-backed PE section", () => { + const original = buildManagedPeFixture(); + const bytes = Buffer.concat([original, Buffer.from([0x2a])]); + const parsed = inspectManagedMembersBytes( + original, + managedPeFixtureTarget(original), + ); + const methodRow = parsed.methods[0]?.row_offset; + expect(methodRow).toBeDefined(); + if (methodRow === undefined) return; + bytes.writeUInt32LE(0x2dff, methodRow); + bytes[0xfff] = 0x06; + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + expect(result.methods[0]?.body).toMatchObject({ + status: "malformed", + header_format: "unknown", + issue: "Method body leaves file-backed PE section data", + anchors: [], + }); + }); + + it("does not decode overlay exception sections after the file-backed PE section", () => { + const original = buildManagedPeFixture(); + const overlay = Buffer.from([0x01, 0x04, 0x00, 0x00]); + const bytes = Buffer.concat([original, overlay]); + const parsed = inspectManagedMembersBytes( + original, + managedPeFixtureTarget(original), + ); + const methodRow = parsed.methods[0]?.row_offset; + expect(methodRow).toBeDefined(); + if (methodRow === undefined) return; + const body = Buffer.alloc(16); + body.writeUInt16LE(0x301b, 0); + body.writeUInt16LE(8, 2); + body.writeUInt32LE(1, 4); + body[12] = 0x2a; + body.copy(bytes, 0xff0); + bytes.writeUInt32LE(0x2df0, methodRow); + overlay.copy(bytes, 0x1000); + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + expect(result.methods[0]?.body).toMatchObject({ + status: "malformed", + issue: "Exception section header leaves artifact", + exception_regions: [], + }); + }); + it("keeps the documented decoded-CIL v1 golden vector stable", () => { const il = Buffer.from([0x00, 0x2a]); const tinyBytes = buildManagedPeFixture({ @@ -125,6 +221,218 @@ describe("managed CIL decoding", () => { }); }); +describe("managed exception region bounds", () => { + it("marks out-of-method ranges malformed and preserves the raw EH clause", () => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0x2a]), + tryOffset: 0, + tryLength: 2, + handlerOffset: 0, + }), + ).toMatchObject({ + status: "malformed", + il_size: 1, + exception_regions: [ + { + flags: 0, + try_offset: 0, + try_length: 2, + handler_offset: 0, + handler_length: 1, + class_token: "0x02000001", + }, + ], + issue: "Exception clause try range leaves the method IL body", + }); + }); + + it("uses half-open method bounds and permits zero-length ranges", () => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0x00, 0x00, 0x2a]), + tryOffset: 0, + tryLength: 1, + handlerOffset: 1, + handlerLength: 2, + }), + ).toMatchObject({ status: "present", il_size: 3, issue: null }); + expect( + managedBodyWithRegion({ + il: Buffer.from([0x2a]), + tryOffset: 0, + tryLength: 0, + handlerOffset: 0, + handlerLength: 0, + }), + ).toMatchObject({ status: "present", il_size: 1, issue: null }); + }); + + it.each([ + ["try start", { tryOffset: 1, tryLength: 4, handlerOffset: 5 }], + ["try end", { tryOffset: 0, tryLength: 1, handlerOffset: 5 }], + ["handler start", { tryLength: 5, handlerOffset: 1, handlerLength: 5 }], + ["handler end", { tryLength: 5, handlerOffset: 0, handlerLength: 1 }], + ] as const)( + "preserves clauses with a mid-instruction %s", + (_label, region) => { + const body = managedBodyWithRegion({ + il: Buffer.from([0x20, 0, 0, 0, 0, 0x2a]), + ...region, + }); + expect(body).toMatchObject({ + status: "malformed", + exception_regions: [expect.any(Object)], + issue: + "Exception clause " + + (_label.startsWith("try") ? "try" : "handler") + + " range does not align with CIL instruction boundaries", + }); + }, + ); + + it("rejects a filter start inside an instruction and retains the clause", () => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0x20, 0, 0, 0, 0, 0x2a]), + flags: 1, + tryLength: 5, + handlerOffset: 5, + extra: 1, + }), + ).toMatchObject({ + status: "malformed", + exception_regions: [expect.objectContaining({ filter_offset: 1 })], + issue: + "Exception clause filter range does not align with CIL instruction boundaries", + }); + }); +}); + +describe("managed exception region prefix boundaries", () => { + it("treats a CIL prefix chain and its opcode as one boundary", () => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0xfe, 0x12, 1, 0xfe, 0x13, 0x4a, 0x2a]), + tryOffset: 0, + tryLength: 6, + handlerOffset: 6, + }), + ).toMatchObject({ status: "present", issue: null, il_size: 7 }); + }); + + it("keeps constrained callvirt as a single region boundary", () => { + const il = Buffer.from([0xfe, 0x16, 1, 0, 0, 1, 0x6f, 1, 0, 0, 6, 0x2a]); + expect( + managedBodyWithRegion({ + il, + tryOffset: 0, + tryLength: 11, + handlerOffset: 11, + }), + ).toMatchObject({ status: "present", issue: null, il_size: 12 }); + expect( + managedBodyWithRegion({ + il, + tryOffset: 6, + tryLength: 1, + handlerOffset: 11, + }), + ).toMatchObject({ + status: "malformed", + issue: + "Exception clause try range does not align with CIL instruction boundaries", + }); + }); + + it.each([ + ["start", { tryOffset: 5, tryLength: 1, handlerOffset: 6 }], + [ + "end after first prefix", + { tryOffset: 0, tryLength: 3, handlerOffset: 6 }, + ], + [ + "end after second prefix", + { tryOffset: 0, tryLength: 5, handlerOffset: 6 }, + ], + ] as const)( + "rejects a region %s between a prefix and its opcode", + (_edge, region) => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0xfe, 0x12, 1, 0xfe, 0x13, 0x4a, 0x2a]), + ...region, + }), + ).toMatchObject({ + status: "malformed", + exception_regions: [expect.any(Object)], + issue: + "Exception clause try range does not align with CIL instruction boundaries", + }); + }, + ); +}); + +describe("managed exception region flags and filters", () => { + it("marks reserved clause flags malformed without dropping its bytes", () => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0x00, 0x2a]), + flags: 8, + tryLength: 1, + handlerOffset: 1, + }), + ).toMatchObject({ + status: "malformed", + exception_regions: [expect.objectContaining({ flags: 8 })], + issue: "Exception clause has unsupported flags 0x8", + }); + }); + + it.each([2, 4])( + "accepts the defined finally/fault clause flag %s", + (flags) => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0x00, 0x00, 0x2a]), + flags, + tryOffset: 0, + tryLength: 1, + handlerOffset: 1, + handlerLength: 2, + }), + ).toMatchObject({ status: "present", issue: null }); + }, + ); + + it("validates filter ordering", () => { + expect( + managedBodyWithRegion({ + il: Buffer.from([0x00, 0x00, 0xfe, 0x11, 0x2a]), + flags: 1, + tryOffset: 0, + tryLength: 1, + handlerOffset: 4, + extra: 1, + }), + ).toMatchObject({ status: "present", il_size: 5, issue: null }); + expect( + managedBodyWithRegion({ + il: Buffer.from([0x00, 0x00, 0x2a]), + flags: 1, + tryOffset: 0, + tryLength: 1, + handlerOffset: 1, + extra: 1, + }), + ).toMatchObject({ + status: "malformed", + issue: + "Exception clause filter must start before its handler within the method IL body", + }); + }); +}); + describe("managed exception section decoding", () => { it("marks an exception section with a partial clause as malformed", () => { const section = Buffer.alloc(17); @@ -155,18 +463,34 @@ describe("managed exception section decoding", () => { }); }); - it("reports malformed data in a chained section", () => { + it("preserves decoded clauses when a chained section has an unsupported kind", () => { const first = Buffer.alloc(16); first[0] = 0x81; first[1] = first.length; const second = Buffer.from([0x02, 0x04, 0x00, 0x00]); expect(managedBodyWithSections(first, second)).toMatchObject({ status: "malformed", - exception_regions: [], + exception_regions: [expect.objectContaining({ flags: 0 })], issue: "Unsupported method data section kind 2", }); }); + it("preserves decoded clauses when a later section has a partial clause", () => { + const first = Buffer.alloc(16); + first[0] = 0x81; + first[1] = first.length; + first.writeUInt16LE(2, 4); + const later = Buffer.alloc(17); + later[0] = 0x01; + later[1] = later.length; + + expect(managedBodyWithSections(first, later)).toMatchObject({ + status: "malformed", + exception_regions: [expect.objectContaining({ flags: 2 })], + issue: "Exception section size does not contain whole clauses", + }); + }); + it.each([ { name: "unsupported section kind", diff --git a/src/dotnet/ManagedExceptionRegionValidation.ts b/src/dotnet/ManagedExceptionRegionValidation.ts new file mode 100644 index 000000000..e535c84f5 --- /dev/null +++ b/src/dotnet/ManagedExceptionRegionValidation.ts @@ -0,0 +1,86 @@ +import type { ManagedExceptionRegion } from "./ManagedMemberInspectorCore.js"; +import type { ExceptionRegionParseResult } from "./ManagedMemberInstructionDecoder.js"; + +const clauseFlagsIssue = (region: ManagedExceptionRegion): string | null => + [0, 1, 2, 4].includes(region.flags) + ? null + : `Exception clause has unsupported flags 0x${region.flags.toString(16)}`; + +const methodExtentIssue = ( + start: number, + length: number, + ilSize: number, + name: "try" | "handler", +): string | null => + start <= ilSize && length <= ilSize - start + ? null + : `Exception clause ${name} range leaves the method IL body`; + +const instructionBoundaryIssue = ( + start: number, + length: number, + boundaries: ReadonlySet, + name: "try" | "handler" | "filter", +): string | null => { + const end = start + length; + return boundaries.has(start) && boundaries.has(end) + ? null + : `Exception clause ${name} range does not align with CIL instruction boundaries`; +}; + +const filterOrderingIssue = ( + region: ManagedExceptionRegion, + ilSize: number, +): string | null => { + if (region.flags !== 1) return null; + const offset = region.filter_offset; + return offset !== null && offset < ilSize && offset < region.handler_offset + ? null + : "Exception clause filter must start before its handler within the method IL body"; +}; + +const clauseIssue = ( + region: ManagedExceptionRegion, + ilSize: number, + boundaries: ReadonlySet, +): string | null => + clauseFlagsIssue(region) ?? + methodExtentIssue(region.try_offset, region.try_length, ilSize, "try") ?? + instructionBoundaryIssue( + region.try_offset, + region.try_length, + boundaries, + "try", + ) ?? + methodExtentIssue( + region.handler_offset, + region.handler_length, + ilSize, + "handler", + ) ?? + instructionBoundaryIssue( + region.handler_offset, + region.handler_length, + boundaries, + "handler", + ) ?? + (region.flags === 1 && region.filter_offset !== null + ? instructionBoundaryIssue(region.filter_offset, 0, boundaries, "filter") + : null) ?? + filterOrderingIssue(region, ilSize); + +/** Validate EH extents and instruction boundaries while retaining each clause. */ +export const validateExceptionRegionRanges = ( + parsed: ExceptionRegionParseResult, + ilSize: number, + instructionOffsets: readonly number[], +): ExceptionRegionParseResult => { + if (parsed.status === "malformed") return parsed; + const boundaries = new Set([...instructionOffsets, ilSize]); + for (const region of parsed.regions) { + const issue = clauseIssue(region, ilSize, boundaries); + if (issue !== null) + return { status: "malformed", regions: parsed.regions, issue }; + } + return parsed; +}; diff --git a/src/dotnet/ManagedMemberInspector.test.ts b/src/dotnet/ManagedMemberInspector.test.ts index 7d8124560..59c3adcf6 100644 --- a/src/dotnet/ManagedMemberInspector.test.ts +++ b/src/dotnet/ManagedMemberInspector.test.ts @@ -7,6 +7,28 @@ import { } from "./ManagedPe.fixture.js"; describe("managed member inspection", () => { + it("keeps metadata handles distinct from calls and field accesses", () => { + const il = Buffer.from([ + 0xd0, 0x01, 0, 0, 0x06, 0x26, 0xd0, 0x01, 0, 0, 0x04, 0x26, 0x28, 0x01, 0, + 0, 0x0a, 0x7b, 0x01, 0, 0, 0x04, 0x26, 0x2a, + ]); + const bytes = buildManagedPeFixture({ + ilBody: Buffer.concat([Buffer.from([(il.length << 2) | 2]), il]), + }); + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.methods[0]?.body.anchors.map(({ opcode }) => opcode)).toEqual( + ["ldtoken", "ldtoken", "call", "ldfld"], + ); + expect(result.call_edges.map(({ opcode }) => opcode)).toEqual(["call"]); + expect(result.field_accesses.map(({ opcode }) => opcode)).toEqual([ + "ldfld", + ]); + }); + it("inspects metadata members, signatures, CIL hashes, call edges, and field anchors", () => { const bytes = buildManagedPeFixture(); const result = inspectManagedMembersBytes( @@ -111,6 +133,115 @@ describe("managed member inspection", () => { }); }); +describe("managed member reference bounds", () => { + it("retains members and reports an out-of-range TypeDef coded index as partial", () => { + const bytes = buildManagedPeFixture({ extendsRaw: 12 }); + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.types[0]).toMatchObject({ + token: "0x02000001", + extends_token: null, + }); + expect(result.fields).toHaveLength(1); + expect(result.methods).toHaveLength(1); + expect(result.metadata.status).toBe("partial"); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + { + code: "invalid-row", + scope: "metadata.TypeDef:0x02000001", + detail: expect.stringContaining( + "TypeDef Extends coded index 0xc is invalid: coded index selects row 3 in table 2, which has 1 rows", + ), + }, + ], + }); + expect(result.coverage.issues[0]?.offset).toBe( + (result.types[0]?.row_offset ?? 0) + 8, + ); + }); + + it("retains a MemberRef with an out-of-range parent as an unknown partial reference", () => { + const bytes = buildManagedPeFixture({ memberRefParentRaw: 40 }); + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.member_refs[0]).toMatchObject({ + token: "0x0a000001", + parent_token: null, + name: ".ctor", + }); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + { + code: "invalid-row", + scope: "metadata.MemberRef:0x0a000001", + detail: expect.stringContaining( + "MemberRef parent coded index 0x28 is invalid: coded index selects row 5 in table 2, which has 1 rows", + ), + }, + ], + }); + expect(result.coverage.issues[0]?.offset).toBe( + result.member_refs[0]?.row_offset, + ); + }); + + it("reports a null required MemberRef parent without dropping the reference", () => { + const bytes = buildManagedPeFixture({ memberRefParentRaw: 0 }); + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.member_refs[0]).toMatchObject({ + token: "0x0a000001", + parent_token: null, + }); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + code: "invalid-row", + detail: + "MemberRef parent coded index 0x0 is null, but the Class column must reference a row", + }), + ], + }); + }); + + it("decodes deeply nested supported pointer signatures without recursion", () => { + const depth = 8_000; + const bytes = buildManagedPeFixture({ + fieldSignature: Buffer.concat([ + Buffer.from([0x06]), + Buffer.alloc(depth, 0x0f), + Buffer.from([0x08]), + ]), + }); + const result = inspectManagedMembersBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.fields[0]?.signature).toMatchObject({ + kind: "field", + parse_status: "decoded", + field_type: `${"i4"}${"*".repeat(depth)}`, + issue: null, + }); + expect(result.methods[0]?.body.status).toBe("present"); + expect(result.coverage).toMatchObject({ state: "complete", issues: [] }); + }); +}); + it.each([ Buffer.alloc(0), Buffer.from([0]), diff --git a/src/dotnet/ManagedMemberInspector.ts b/src/dotnet/ManagedMemberInspector.ts index 8e3b6ef3d..23a2435ea 100644 --- a/src/dotnet/ManagedMemberInspector.ts +++ b/src/dotnet/ManagedMemberInspector.ts @@ -116,9 +116,11 @@ export const inspectManagedMembersBytes = ( const { layout, inventory } = readMemberInventory(bytes, pe); issues.push(...inventory.issues); const ranges = typeRanges(bytes, layout); - const types = parseTypes(bytes, layout, ranges); + const parsedTypes = parseTypes(bytes, layout, ranges); + issues.push(...parsedTypes.issues); const fields = parseFields(bytes, layout, ranges); const memberRefs = parseMemberRefs(bytes, layout); + issues.push(...memberRefs.issues); const methods = parseMethods({ bytes, layout, @@ -150,7 +152,7 @@ export const inspectManagedMembersBytes = ( requires_artifact_sha256: target.sha256, requires_mvid: inventory.module?.mvid ?? null, }, - types, + types: parsedTypes.types, fields: fields.fields, methods: methods.methods, member_refs: memberRefs.refs, diff --git a/src/dotnet/ManagedMemberInspectorCore.ts b/src/dotnet/ManagedMemberInspectorCore.ts index 227a93cc9..db17acdc1 100644 --- a/src/dotnet/ManagedMemberInspectorCore.ts +++ b/src/dotnet/ManagedMemberInspectorCore.ts @@ -2,7 +2,10 @@ import { managedFailure, ManagedReaderFailure, } from "./ManagedReaderFailure.js"; -import type { ManagedMemberInspection } from "../domain/managed/managedArtifact.js"; +import type { + ManagedMemberInspection, + ManagedParseIssue, +} from "../domain/managed/managedArtifact.js"; import { type ManagedMetadataLayout, type MetadataTableLayout, @@ -10,6 +13,7 @@ import { import { metadataRowCursor, metadataCodedToken, + metadataCodedTokenInvalidReason, metadataToken, readMetadataString, sha256Bytes, @@ -190,11 +194,15 @@ export const parseTypes = ( bytes: Buffer, layout: ManagedMetadataLayout, ranges: readonly TypeRange[], -): readonly ManagedType[] => { +): { + readonly types: readonly ManagedType[]; + readonly issues: readonly ManagedParseIssue[]; +} => { const typeTable = layout.table(2); const fields = layout.table(4); const methods = layout.table(6); const items: ManagedType[] = []; + const issues: ManagedParseIssue[] = []; for (let row = 1; row <= (typeTable?.rowCount ?? 0); row += 1) { const cursor = metadataRowCursor(bytes, layout, 2, row); const flags = cursor.readUInt32(); @@ -210,6 +218,7 @@ export const parseTypes = ( cursor.readIndex(layout.stringIndexSize), layout.strings.size, ); + const extendsOffset = cursor.offset; const extendsRaw = cursor.readIndex(layout.codedIndexSize("TypeDefOrRef")); const fieldStart = cursor.readIndex(layout.tableIndexSize(4)); const methodStart = cursor.readIndex(layout.tableIndexSize(6)); @@ -220,6 +229,19 @@ export const parseTypes = ( methodStart, nextRange?.methodStart ?? 0, ); + const extendsReason = metadataCodedTokenInvalidReason( + extendsRaw, + 2, + [2, 1, 27], + layout.rowCounts, + ); + if (extendsReason !== null) + issues.push({ + code: "invalid-row", + scope: `metadata.TypeDef:${metadataToken(2, row)}`, + offset: extendsOffset, + detail: `TypeDef Extends coded index 0x${extendsRaw.toString(16)} is invalid: ${extendsReason}`, + }); items.push({ token: metadataToken(2, row), row_offset: cursor.start, @@ -227,7 +249,12 @@ export const parseTypes = ( name, full_name: fullName(namespace, name), flags, - extends_token: metadataCodedToken(extendsRaw, 2, [2, 1, 27]), + extends_token: metadataCodedToken( + extendsRaw, + 2, + [2, 1, 27], + layout.rowCounts, + ), field_list: { first_row: fieldRange.first, last_row: fieldRange.last, @@ -240,7 +267,7 @@ export const parseTypes = ( }, }); } - return items; + return { types: items, issues }; }; const readCompressed = ( @@ -301,55 +328,48 @@ const ELEMENT_TYPES = new Map([ [0x1c, "object"], ]); -const ELEMENT_TYPE_SUFFIXES = new Map([ - [0x0f, "*"], - [0x10, "&"], -]); - const readTypeSignature = ( blob: Buffer, offset: number, ): { readonly value: string; readonly next: number } => { - const kind = blob[offset]; + const suffixes: string[] = []; + let cursor = offset; + let kind = blob[cursor]; + while (kind === 0x0f || kind === 0x10 || kind === 0x1d) { + suffixes.push(kind === 0x0f ? "*" : kind === 0x10 ? "&" : "[]"); + cursor += 1; + kind = blob[cursor]; + } if (kind === undefined) throw managedFailure( "invalid-blob", "signature", "truncated type signature", - offset, + cursor, ); const named = ELEMENT_TYPES.get(kind); - if (named !== undefined) return { value: named, next: offset + 1 }; - const suffix = ELEMENT_TYPE_SUFFIXES.get(kind); - if (suffix !== undefined) { - const inner = readTypeSignature(blob, offset + 1); - return { value: `${inner.value}${suffix}`, next: inner.next }; + let value: string; + let next: number; + if (named !== undefined) { + value = named; + next = cursor + 1; + } else if (kind === 0x11 || kind === 0x12) { + const token = readCompressed(blob, cursor + 1); + value = `${kind === 0x11 ? "valuetype" : "class"}:${String(token.value)}`; + next = token.next; + } else if (kind === 0x1e || kind === 0x13) { + const variable = readCompressed(blob, cursor + 1); + value = `${kind === 0x1e ? "mvar" : "var"}:${String(variable.value)}`; + next = variable.next; + } else { + throw managedFailure( + "unsupported-signature", + "signature", + `unsupported element type 0x${kind.toString(16)}`, + cursor, + ); } - if (kind === 0x11 || kind === 0x12) { - const token = readCompressed(blob, offset + 1); - return { - value: `${kind === 0x11 ? "valuetype" : "class"}:${String(token.value)}`, - next: token.next, - }; - } - if (kind === 0x1d) { - const inner = readTypeSignature(blob, offset + 1); - return { value: `${inner.value}[]`, next: inner.next }; - } - if (kind === 0x1e) { - const variable = readCompressed(blob, offset + 1); - return { value: `mvar:${String(variable.value)}`, next: variable.next }; - } - if (kind === 0x13) { - const variable = readCompressed(blob, offset + 1); - return { value: `var:${String(variable.value)}`, next: variable.next }; - } - throw managedFailure( - "unsupported-signature", - "signature", - `unsupported element type 0x${kind.toString(16)}`, - offset, - ); + return { value: value + suffixes.reverse().join(""), next }; }; const callingConvention = (value: number): string => { diff --git a/src/dotnet/ManagedMemberInstructionDecoder.ts b/src/dotnet/ManagedMemberInstructionDecoder.ts index ea61cc289..7c8a11a05 100644 --- a/src/dotnet/ManagedMemberInstructionDecoder.ts +++ b/src/dotnet/ManagedMemberInstructionDecoder.ts @@ -9,6 +9,14 @@ const opcode = ( ): { readonly name: string; readonly operand: string } | undefined => OPCODES.get(code); +const PREFIX_OPCODES = new Set([ + "unaligned.", + "volatile.", + "tail.", + "constrained.", + "readonly.", +]); + const OPCODES = new Map< number, { readonly name: string; readonly operand: string } @@ -379,12 +387,15 @@ export const decodeInstructions = ( il: Buffer, ): { readonly parsed: readonly ParsedInstruction[]; + readonly instructionStarts: readonly number[]; readonly count: number; readonly truncated: number; readonly issue: string | null; } => { const parsed: ParsedInstruction[] = []; + const instructionStarts: number[] = []; let offset = 0; + let prefixStart: number | null = null; let issue: string | null = null; try { while (offset < il.length) { @@ -403,6 +414,12 @@ export const decodeInstructions = ( break; } offset = operand.next; + if (PREFIX_OPCODES.has(descriptor.name)) { + prefixStart ??= start; + } else { + instructionStarts.push(prefixStart ?? start); + prefixStart = null; + } parsed.push({ offset: start, opcode: descriptor.name, @@ -415,7 +432,7 @@ export const decodeInstructions = ( cause instanceof Error ? cause.message : "Instruction decode failed"; } const truncated = offset < il.length && issue === null ? 1 : 0; - return { parsed, count: parsed.length, truncated, issue }; + return { parsed, instructionStarts, count: parsed.length, truncated, issue }; }; export const parseExceptionRegions = ( @@ -435,12 +452,14 @@ export const parseExceptionRegions = ( ) return malformedExceptionRegions( "Exception section header leaves artifact", + regions, ); const kind = bytes.readUInt8(sectionOffset); if ((kind & 0x3f) !== 1) return malformedExceptionRegions( `Unsupported method data section kind ${String(kind & 0x3f)}`, + regions, ); const fat = (kind & 0x40) !== 0; const size = fat @@ -456,10 +475,12 @@ export const parseExceptionRegions = ( ) return malformedExceptionRegions( "Exception section size leaves artifact", + regions, ); if ((size - 4) % clauseSize !== 0) return malformedExceptionRegions( "Exception section size does not contain whole clauses", + regions, ); if ( !fat && @@ -467,6 +488,7 @@ export const parseExceptionRegions = ( ) return malformedExceptionRegions( "Small exception section reserved bytes are nonzero", + regions, ); const count = (size - 4) / clauseSize; @@ -478,24 +500,21 @@ export const parseExceptionRegions = ( return { status: "complete", regions }; }; -interface ExceptionRegionParseSuccess { - readonly status: "complete"; - readonly regions: ManagedExceptionRegion[]; -} - -interface ExceptionRegionParseFailure { - readonly status: "malformed"; - readonly regions: []; - readonly issue: string; -} - -type ExceptionRegionParseResult = - | ExceptionRegionParseSuccess - | ExceptionRegionParseFailure; +export type ExceptionRegionParseResult = + | { + readonly status: "complete"; + readonly regions: ManagedExceptionRegion[]; + } + | { + readonly status: "malformed"; + readonly regions: ManagedExceptionRegion[]; + readonly issue: string; + }; const malformedExceptionRegions = ( issue: string, -): ExceptionRegionParseFailure => ({ status: "malformed", regions: [], issue }); + regions: ManagedExceptionRegion[] = [], +): ExceptionRegionParseResult => ({ status: "malformed", regions, issue }); const readExceptionClauses = ( bytes: Buffer, diff --git a/src/dotnet/ManagedMemberRows.ts b/src/dotnet/ManagedMemberRows.ts index d948e54f7..e9cb8019c 100644 --- a/src/dotnet/ManagedMemberRows.ts +++ b/src/dotnet/ManagedMemberRows.ts @@ -1,4 +1,5 @@ import type { ManagedPeLayout } from "./ManagedPeReader.js"; +import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js"; import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js"; import { createDeclaringTypeLookup, @@ -17,6 +18,7 @@ import { metadataToken, metadataRowCursor, metadataCodedToken, + metadataCodedTokenInvalidReason, readMetadataBlob, readMetadataString, } from "./ManagedMetadataHeaps.js"; @@ -71,9 +73,11 @@ export const parseMemberRefs = ( ): { readonly refs: readonly ManagedMemberRef[]; readonly core: ReadonlyMap; + readonly issues: readonly ManagedParseIssue[]; } => { const refs: ManagedMemberRef[] = []; const core = new Map(); + const issues: ManagedParseIssue[] = []; const table = layout.table(10); for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) { const cursor = metadataRowCursor(bytes, layout, 10, row); @@ -93,16 +97,37 @@ export const parseMemberRefs = ( layout.blob.size, ); const token = metadataToken(10, row); + const parentReason = metadataCodedTokenInvalidReason( + parentRaw, + 3, + [2, 1, 26, 6, 27], + layout.rowCounts, + ); + if (parentReason !== null || parentRaw === 0) + issues.push({ + code: "invalid-row", + scope: `metadata.MemberRef:${token}`, + offset: cursor.start, + detail: + parentReason === null + ? "MemberRef parent coded index 0x0 is null, but the Class column must reference a row" + : `MemberRef parent coded index 0x${parentRaw.toString(16)} is invalid: ${parentReason}`, + }); refs.push({ token, row_offset: cursor.start, - parent_token: metadataCodedToken(parentRaw, 3, [2, 1, 26, 6, 27]), + parent_token: metadataCodedToken( + parentRaw, + 3, + [2, 1, 26, 6, 27], + layout.rowCounts, + ), name, signature: signature(sig), }); core.set(token, { token, name }); } - return { refs, core }; + return { refs, core, issues }; }; interface ParseMethodsInput { diff --git a/src/dotnet/ManagedMetadataHandles.test.ts b/src/dotnet/ManagedMetadataHandles.test.ts deleted file mode 100644 index e5fc60fa5..000000000 --- a/src/dotnet/ManagedMetadataHandles.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { expect, it } from "vitest"; -import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js"; -import { - buildManagedPeFixture, - managedPeFixtureTarget, -} from "./ManagedPe.fixture.js"; - -it("retains metadata handles without reporting calls or field accesses", () => { - const il = Buffer.from([ - 0xd0, 0x01, 0, 0, 0x06, 0x26, 0xd0, 0x01, 0, 0, 0x04, 0x26, 0x28, 0x01, 0, - 0, 0x0a, 0x7b, 0x01, 0, 0, 0x04, 0x26, 0x2a, - ]); - const bytes = buildManagedPeFixture({ - ilBody: Buffer.concat([Buffer.from([(il.length << 2) | 2]), il]), - }); - const result = inspectManagedMembersBytes( - bytes, - managedPeFixtureTarget(bytes), - ); - expect( - result.methods[0]?.body.anchors.map((anchor) => anchor.opcode), - ).toEqual(["ldtoken", "ldtoken", "call", "ldfld"]); - expect(result.call_edges.map((edge) => edge.opcode)).toEqual(["call"]); - expect(result.field_accesses.map((access) => access.opcode)).toEqual([ - "ldfld", - ]); -}); diff --git a/src/dotnet/ManagedMetadataHeaps.ts b/src/dotnet/ManagedMetadataHeaps.ts index 9994a41a4..ab59caa21 100644 --- a/src/dotnet/ManagedMetadataHeaps.ts +++ b/src/dotnet/ManagedMetadataHeaps.ts @@ -253,10 +253,32 @@ export const metadataCodedToken = ( raw: number, bits: number, tables: readonly (number | undefined)[], + rowCounts: readonly number[], ): string | null => { if (raw === 0) return null; const tag = raw & (2 ** bits - 1); const row = Math.floor(raw / 2 ** bits); const table = tables[tag]; - return table === undefined || row === 0 ? null : metadataToken(table, row); + return table === undefined || row === 0 || row > (rowCounts[table] ?? 0) + ? null + : metadataToken(table, row); +}; + +/** Explain why a non-null coded index cannot name an admitted metadata row. */ +export const metadataCodedTokenInvalidReason = ( + raw: number, + bits: number, + tables: readonly (number | undefined)[], + rowCounts: readonly number[], +): string | null => { + if (raw === 0) return null; + const tag = raw & (2 ** bits - 1); + const row = Math.floor(raw / 2 ** bits); + const table = tables[tag]; + if (table === undefined) return `coded index tag ${tag} is not defined`; + if (row === 0) return `coded index selects row zero in table ${table}`; + const count = rowCounts[table] ?? 0; + return row > count + ? `coded index selects row ${row} in table ${table}, which has ${count} rows` + : null; }; diff --git a/src/dotnet/ManagedMetadataInventory.ts b/src/dotnet/ManagedMetadataInventory.ts index f0eadaaa0..a347fe620 100644 --- a/src/dotnet/ManagedMetadataInventory.ts +++ b/src/dotnet/ManagedMetadataInventory.ts @@ -73,17 +73,48 @@ const uniqueIssues = ( const heapExtent = (layout: ManagedMetadataLayout): number => Math.max(layout.strings.size, layout.blob.size); +const validateIdentityTableCounts = ( + layout: ManagedMetadataLayout, + issues: ManagedParseIssue[], +): void => { + const moduleRows = layout.table(0)?.rowCount ?? 0; + if (moduleRows !== 1) + issues.push({ + code: "invalid-row", + scope: "metadata.Module", + offset: layout.table(0)?.offset ?? null, + detail: `Module table must contain exactly one row; found ${String(moduleRows)}`, + }); + + const assemblyRows = layout.table(32)?.rowCount ?? 0; + if (assemblyRows > 1) + issues.push({ + code: "invalid-row", + scope: "metadata.Assembly", + offset: layout.table(32)?.offset ?? null, + detail: `Assembly table can contain at most one row; found ${String(assemblyRows)}`, + }); +}; + const readReferences = ( bytes: Buffer, layout: ManagedMetadataLayout, issues: ManagedParseIssue[], -): readonly AssemblyReference[] => - readRows(layout.table(35), (row) => +): { + readonly references: readonly AssemblyReference[]; + readonly referenceNames: readonly string[]; +} => { + const references = readRows(layout.table(35), (row) => safeRead( () => readAssemblyReference(bytes, layout, row, heapExtent(layout)), issues, ), ); + return { + references, + referenceNames: [...new Set(references.map(({ name }) => name))].sort(), + }; +}; const readResources = ({ bytes, @@ -114,53 +145,27 @@ const readAttributes = ( bytes: Buffer, layout: ManagedMetadataLayout, issues: ManagedParseIssue[], -): readonly CustomAttribute[] => - readRows(layout.table(12), (row) => +): { + readonly attributes: readonly CustomAttribute[]; + readonly targetFrameworks: readonly string[]; +} => { + const attributes = readRows(layout.table(12), (row) => safeRead( () => readCustomAttribute(bytes, layout, row, heapExtent(layout)), issues, ), ); - -const collectReferenceNames = ( - bytes: Buffer, - layout: ManagedMetadataLayout, - issues: ManagedParseIssue[], -): readonly string[] => { - const names: string[] = []; - const referenceTable = layout.table(35); - for (let row = 1; row <= (referenceTable?.rowCount ?? 0); row += 1) { - const reference = safeRead( - () => readAssemblyReference(bytes, layout, row, heapExtent(layout)), - issues, - ); - if (reference !== undefined) names.push(reference.name); - } - return names; -}; - -const collectTargetFrameworks = ( - bytes: Buffer, - layout: ManagedMetadataLayout, - issues: ManagedParseIssue[], -): readonly string[] => { - const targetFrameworks: string[] = []; - const attributeTable = layout.table(12); - for (let row = 1; row <= (attributeTable?.rowCount ?? 0); row += 1) { - const attribute = safeRead( - () => readCustomAttribute(bytes, layout, row, heapExtent(layout)), - issues, - ); + const targetFrameworks = new Set(); + for (const attribute of attributes) { if ( - attribute?.parent_token === metadataToken(32, 1) && + attribute.parent_token === metadataToken(32, 1) && attribute.type_name === "System.Runtime.Versioning.TargetFrameworkAttribute" && - attribute.decoded_fixed_string !== null && - !targetFrameworks.includes(attribute.decoded_fixed_string) + attribute.decoded_fixed_string !== null ) - targetFrameworks.push(attribute.decoded_fixed_string); + targetFrameworks.add(attribute.decoded_fixed_string); } - return targetFrameworks; + return { attributes, targetFrameworks: [...targetFrameworks].sort() }; }; /** Inventory identity tables without CLR reflection or execution. */ @@ -170,27 +175,30 @@ export const readManagedMetadataInventory = ( resourceDirectory: ManagedResourceDirectory | null, ): ManagedMetadataInventory => { const issues: ManagedParseIssue[] = []; + validateIdentityTableCounts(layout, issues); const module = safeRead(() => readModule(bytes, layout, heapExtent(layout)), issues) ?? null; const assembly = safeRead(() => readAssembly(bytes, layout, heapExtent(layout)), issues) ?? null; - const references = readReferences(bytes, layout, issues); + const { references, referenceNames } = readReferences(bytes, layout, issues); const resources = readResources({ bytes, layout, resourceDirectory, issues, }); - const attributes = readAttributes(bytes, layout, issues); - const referenceNames = collectReferenceNames(bytes, layout, issues); - const targetFrameworks = collectTargetFrameworks(bytes, layout, issues); + const { attributes, targetFrameworks } = readAttributes( + bytes, + layout, + issues, + ); return { module, assembly, - targetFrameworks: [...targetFrameworks].sort(), - referenceNames: [...new Set(referenceNames)].sort(), + targetFrameworks, + referenceNames, references, resources, attributes, diff --git a/src/dotnet/ManagedMetadataInventoryRows.ts b/src/dotnet/ManagedMetadataInventoryRows.ts index 836c9a376..596cb519c 100644 --- a/src/dotnet/ManagedMetadataInventoryRows.ts +++ b/src/dotnet/ManagedMetadataInventoryRows.ts @@ -9,6 +9,7 @@ import { import { metadataRowCursor, metadataCodedToken, + metadataCodedTokenInvalidReason, metadataToken, readMetadataBlob, readMetadataGuid, @@ -365,6 +366,7 @@ export const readCustomAttribute = ( const parentRaw = cursor.readIndex( layout.codedIndexSize("HasCustomAttribute"), ); + const typeOffset = cursor.offset; const typeRaw = cursor.readIndex( layout.codedIndexSize("CustomAttributeType"), ); @@ -381,23 +383,48 @@ export const readCustomAttribute = ( 6, 4, 1, 2, 8, 9, 10, 0, 14, 23, 20, 17, 26, 27, 32, 35, 38, 39, 40, 42, 44, 43, ], + layout.rowCounts, + ); + const parentReason = metadataCodedTokenInvalidReason( + parentRaw, + 5, + [ + 6, 4, 1, 2, 8, 9, 10, 0, 14, 23, 20, 17, 26, 27, 32, 35, 38, 39, 40, 42, + 44, 43, + ], + layout.rowCounts, ); if (parent === null) throw managedFailure( "invalid-row", - "metadata.CustomAttribute", - "CustomAttribute parent coded index is invalid", + `metadata.CustomAttribute:${metadataToken(12, row)}`, + `CustomAttribute parent coded index 0x${parentRaw.toString(16)} is invalid${parentReason === null ? " because the required parent is null" : `: ${parentReason}`}`, cursor.start, ); + const typeReason = metadataCodedTokenInvalidReason( + typeRaw, + 3, + [undefined, undefined, 6, 10], + layout.rowCounts, + ); + if (typeReason !== null || typeRaw === 0) + throw managedFailure( + "invalid-row", + `metadata.CustomAttribute:${metadataToken(12, row)}`, + typeReason === null + ? "CustomAttribute constructor coded index 0x0 is null, but Type must reference a MethodDef or MemberRef row" + : `CustomAttribute constructor coded index 0x${typeRaw.toString(16)} is invalid: ${typeReason}`, + typeOffset, + ); const typeName = attributeTypeName(bytes, layout, typeRaw, heapExtent); return { parent_token: parent, - constructor_token: metadataCodedToken(typeRaw, 3, [ - undefined, - undefined, - 6, - 10, - ]), + constructor_token: metadataCodedToken( + typeRaw, + 3, + [undefined, undefined, 6, 10], + layout.rowCounts, + ), type_name: typeName, value_length: value.length, value_sha256: sha256Bytes(value), @@ -434,6 +461,7 @@ export const readResource = ({ cursor.readIndex(layout.stringIndexSize), Math.max(layout.strings.size, layout.blob.size), ); + const implementationOffset = cursor.offset; const implementationRaw = cursor.readIndex( layout.codedIndexSize("Implementation"), ); @@ -441,7 +469,21 @@ export const readResource = ({ implementationRaw, 2, [38, 35, 39], + layout.rowCounts, ); + const implementationReason = metadataCodedTokenInvalidReason( + implementationRaw, + 2, + [38, 35, 39], + layout.rowCounts, + ); + if (implementationReason !== null) + issues.push({ + code: "invalid-row", + scope: `metadata.ManifestResource:${metadataToken(40, row)}`, + offset: implementationOffset, + detail: `ManifestResource implementation coded index 0x${implementationRaw.toString(16)} is invalid: ${implementationReason}`, + }); let dataLength: number | null = null; let dataSha256: string | null = null; if (implementationRaw === 0) { diff --git a/src/dotnet/ManagedMethodBodyReader.ts b/src/dotnet/ManagedMethodBodyReader.ts index b8f0d2948..fd1c5e642 100644 --- a/src/dotnet/ManagedMethodBodyReader.ts +++ b/src/dotnet/ManagedMethodBodyReader.ts @@ -4,6 +4,7 @@ import { decodeInstructions, parseExceptionRegions, } from "./ManagedMemberInstructionDecoder.js"; +import { validateExceptionRegionRanges } from "./ManagedExceptionRegionValidation.js"; import { sha256Bytes } from "./ManagedMetadataHeaps.js"; const bodyStatus = ( @@ -60,6 +61,19 @@ const readMethodBodyHeader = ( }; }; +const readExceptionRegions = ( + bytes: Buffer, + sectionOffset: number, + methodEnd: number, + ilSize: number, + instructionOffsets: readonly number[], +): ReturnType => + validateExceptionRegionRanges( + parseExceptionRegions(bytes, sectionOffset, methodEnd), + ilSize, + instructionOffsets, + ); + /** Decode admitted managed CIL, retaining unavailable implementation metadata as partial. */ export const methodBody = ( bytes: Buffer, @@ -82,7 +96,13 @@ export const methodBody = ( ); try { const offset = pe.rvaToOffset(rva, 1, "method.body"); + const methodExtent = pe.rvaAvailableBytes(rva, "method.body"); const header = readMethodBodyHeader(bytes, offset); + if ( + header.size > methodExtent || + header.ilSize > methodExtent - header.size + ) + throw new RangeError("Method body leaves file-backed PE section data"); const ilOffset = offset + header.size; if (ilOffset > bytes.length - header.ilSize) throw new RangeError("Method IL bytes leave artifact"); @@ -114,7 +134,13 @@ export const methodBody = ( const sectionOffset = (methodEnd + 3) & ~3; const exceptionRegions = header.format === "fat" && (header.flags & 8) !== 0 - ? parseExceptionRegions(bytes, sectionOffset, bytes.length) + ? readExceptionRegions( + bytes, + sectionOffset, + offset + methodExtent, + header.ilSize, + decoded.instructionStarts, + ) : null; const status = bodyStatus( decoded.issue, diff --git a/src/dotnet/ManagedNativeBoundaryHelpers.ts b/src/dotnet/ManagedNativeBoundaryHelpers.ts index 5ef2e3bbb..3ba7d81c4 100644 --- a/src/dotnet/ManagedNativeBoundaryHelpers.ts +++ b/src/dotnet/ManagedNativeBoundaryHelpers.ts @@ -8,6 +8,7 @@ import { type ManagedMetadataLayout } from "./ManagedMetadataLayout.js"; import { metadataRowCursor, metadataCodedToken, + metadataCodedTokenInvalidReason, metadataToken, readMetadataString, } from "./ManagedMetadataHeaps.js"; @@ -127,6 +128,7 @@ interface ParseImplMapsContext { readonly heapExtent: number; readonly modules: readonly ModuleRef[]; readonly members: ReadonlyMap; + readonly issues: ManagedParseIssue[]; } export const parseImplMaps = ({ @@ -135,6 +137,7 @@ export const parseImplMaps = ({ heapExtent, modules, members, + issues, }: ParseImplMapsContext): readonly NativeImport[] => { const moduleNames = new Map(modules.map((module) => [module.token, module])); const imports: NativeImport[] = []; @@ -142,10 +145,31 @@ export const parseImplMaps = ({ for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) { const cursor = metadataRowCursor(bytes, layout, 28, row); const mappingFlags = cursor.readUInt16(); - const memberToken = metadataCodedToken( - cursor.readIndex(layout.codedIndexSize("MemberForwarded")), + const memberForwardedOffset = cursor.offset; + const memberForwardedRaw = cursor.readIndex( + layout.codedIndexSize("MemberForwarded"), + ); + const memberForwardedReason = metadataCodedTokenInvalidReason( + memberForwardedRaw, 1, [4, 6], + layout.rowCounts, + ); + if (memberForwardedReason !== null || memberForwardedRaw === 0) + issues.push({ + code: "invalid-row", + scope: `metadata.ImplMap:${metadataToken(28, row)}`, + offset: memberForwardedOffset, + detail: + memberForwardedReason === null + ? "ImplMap MemberForwarded coded index 0x0 is null, but the column must reference a Field or MethodDef row" + : `ImplMap MemberForwarded coded index 0x${memberForwardedRaw.toString(16)} is invalid: ${memberForwardedReason}`, + }); + const memberToken = metadataCodedToken( + memberForwardedRaw, + 1, + [4, 6], + layout.rowCounts, ); const importName = readMetadataString( bytes, diff --git a/src/dotnet/ManagedNativeBoundaryInspector.test.ts b/src/dotnet/ManagedNativeBoundaryInspector.test.ts index 70c49248d..1f2a702ef 100644 --- a/src/dotnet/ManagedNativeBoundaryInspector.test.ts +++ b/src/dotnet/ManagedNativeBoundaryInspector.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; +import { createHash } from "node:crypto"; + +import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js"; import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js"; import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInspector.js"; import { @@ -68,11 +71,74 @@ describe("managed native boundaries", () => { ready_to_run: true, mixed_mode_or_native_header: true, }); + expect(result.coverage).toMatchObject({ state: "complete", issues: [] }); expect(result.limitations).toContain( "P/Invoke rows prove managed import declarations only; this inspection does not verify that a native library, export, thunk, or provider-qualified function exists.", ); }); +}); +describe("managed native boundary coded indexes", () => { + it("retains P/Invoke imports with an invalid MemberForwarded index as partial evidence", () => { + const bytes = buildManagedPeFixture({ + pinvoke: { memberForwardedRaw: 5 }, + }); + const result = inspectManagedNativeBoundariesBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.pinvoke_imports).toMatchObject([ + { + member_token: null, + member_kind: "unknown", + member_name: null, + import_name: "MessageBoxW", + }, + ]); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + code: "invalid-row", + scope: "metadata.ImplMap:0x1c000001", + detail: expect.stringContaining( + "coded index 0x5 is invalid: coded index selects row 2 in table 6, which has 1 rows", + ), + }), + ], + }); + expect(result.coverage.issues[0]?.offset).toBe( + (result.pinvoke_imports[0]?.row_offset ?? 0) + 2, + ); + }); + + it("reports null for the required MemberForwarded index", () => { + const bytes = buildManagedPeFixture({ + pinvoke: { memberForwardedRaw: 0 }, + }); + const result = inspectManagedNativeBoundariesBytes( + bytes, + managedPeFixtureTarget(bytes), + ); + + expect(result.pinvoke_imports).toMatchObject([ + { member_token: null, member_kind: "unknown" }, + ]); + expect(result.coverage).toMatchObject({ + state: "partial", + issues: [ + expect.objectContaining({ + code: "invalid-row", + detail: + "ImplMap MemberForwarded coded index 0x0 is null, but the column must reference a Field or MethodDef row", + }), + ], + }); + }); +}); + +describe("managed native boundary inspection", () => { it("returns complete member inventories inline and reports unavailable metadata", () => { const bytes = buildManagedPeFixture(); const paged = inspectManagedMembersBytes( @@ -158,3 +224,34 @@ describe("managed native boundaries", () => { ); }); }); + +describe("managed PE fixture layout", () => { + it("keeps ReadyToRun metadata separate from expanded metadata and resources", () => { + const resourceData = Buffer.alloc(64 * 1024, 0x5a); + const bytes = buildManagedPeFixture({ + readyToRun: true, + references: Array.from( + { length: 2_200 }, + (_, index) => `Reference.${String(index).padStart(4, "0")}`, + ), + resourceData, + }); + const target = managedPeFixtureTarget(bytes); + const artifact = inspectManagedArtifactBytes(bytes, target); + const boundaries = inspectManagedNativeBoundariesBytes(bytes, target); + + expect(artifact.coverage).toMatchObject({ state: "complete", issues: [] }); + expect(artifact.references).toHaveLength(2_200); + expect(artifact.resources[0]).toMatchObject({ + data_length: resourceData.length, + data_sha256: createHash("sha256").update(resourceData).digest("hex"), + }); + expect(boundaries.cli_native).toMatchObject({ + ready_to_run_signature: true, + managed_native_header_size: 4, + }); + expect(boundaries.cli_native.managed_native_header_rva).toBeGreaterThan( + 0x2700, + ); + }); +}); diff --git a/src/dotnet/ManagedNativeBoundaryInspector.ts b/src/dotnet/ManagedNativeBoundaryInspector.ts index a0e58f3c0..2bed713ba 100644 --- a/src/dotnet/ManagedNativeBoundaryInspector.ts +++ b/src/dotnet/ManagedNativeBoundaryInspector.ts @@ -156,6 +156,7 @@ export const inspectManagedNativeBoundariesBytes = ( }); } const heapExtent = Math.max(layout.strings.size, layout.blob.size); + const issues = [...inventory.issues]; const moduleRefs = parseModuleRefs(bytes, layout, heapExtent); const members = new Map([ ...parseFields(bytes, layout, heapExtent), @@ -167,6 +168,7 @@ export const inspectManagedNativeBoundariesBytes = ( heapExtent, modules: moduleRefs, members, + issues, }); const pinvokeTokens = new Set( imports @@ -187,6 +189,6 @@ export const inspectManagedNativeBoundariesBytes = ( imports, implementations, native: cliNative(pe), - issues: [], + issues, }); }; diff --git a/src/dotnet/ManagedPe.fixture.ts b/src/dotnet/ManagedPe.fixture.ts index 076a4cd8e..05d035ab6 100644 --- a/src/dotnet/ManagedPe.fixture.ts +++ b/src/dotnet/ManagedPe.fixture.ts @@ -5,22 +5,31 @@ import type { BinaryTarget } from "../domain/binaryTarget.js"; interface ManagedPeFixtureOptions { readonly cliFlags?: number; readonly corruptMetadataSignature?: boolean; + readonly customAttributeTypeRaw?: number; + readonly extendsRaw?: number; readonly fieldName?: string; readonly fieldSignature?: Buffer; readonly ilBody?: Buffer; + readonly malformedAssemblyReferenceRows?: readonly number[]; + readonly malformedCustomAttributeRows?: readonly number[]; readonly metadataValidMaskExtra?: bigint; readonly methodName?: string; readonly methodSignature?: Buffer; + readonly moduleRowCount?: number; + readonly assemblyRowCount?: number; + readonly memberRefParentRaw?: number; readonly mvid?: Buffer; readonly pinvoke?: { readonly importName?: string; + readonly memberForwardedRaw?: number; readonly mappingFlags?: number; readonly moduleName?: string; }; readonly readyToRun?: boolean; readonly references?: readonly string[]; + readonly resourceImplementationRaw?: number; readonly resourceData?: Buffer; - readonly targetFramework?: string; + readonly targetFramework?: string | null; readonly typeName?: string; readonly typeNamespace?: string; } @@ -35,6 +44,10 @@ class StringHeap { readonly #chunks: Buffer[] = [Buffer.from([0])]; #size = 1; + get size(): number { + return this.#size; + } + add(value: string): number { if (value.length === 0) return 0; const index = this.#size; @@ -53,13 +66,26 @@ class BlobHeap { readonly #chunks: Buffer[] = [Buffer.from([0])]; #size = 1; + get size(): number { + return this.#size; + } + add(value: Buffer): number { if (value.length === 0) return 0; - if (value.length > 0x7f) - throw new RangeError("Managed PE fixture blob is too large"); const index = this.#size; - this.#chunks.push(Buffer.from([value.length]), value); - this.#size += value.length + 1; + const prefix = + value.length <= 0x7f + ? Buffer.from([value.length]) + : value.length <= 0x3fff + ? Buffer.from([0x80 | (value.length >> 8), value.length & 0xff]) + : Buffer.from([ + 0xc0 | (value.length >> 24), + (value.length >> 16) & 0xff, + (value.length >> 8) & 0xff, + value.length & 0xff, + ]); + this.#chunks.push(prefix, value); + this.#size += value.length + prefix.length; return index; } @@ -106,33 +132,92 @@ const u32 = (value: number): Buffer => { return bytes; }; -const moduleRow = (name: number): Buffer => - Buffer.concat([u16(0), u16(name), u16(1), u16(0), u16(0)]); +type MetadataIndexSize = 2 | 4; -const typeRefRow = (name: number, namespace: number): Buffer => - Buffer.concat([u16(0), u16(name), u16(namespace)]); +const indexSize = (rows: number, codedTagBits = 0): MetadataIndexSize => + rows < 2 ** (16 - codedTagBits) ? 2 : 4; -const typeDefRow = ( +const metadataIndex = (value: number, size: MetadataIndexSize): Buffer => + size === 2 ? u16(value) : u32(value); + +const codedIndexSize = ( + rows: readonly number[], + tagBits: number, +): MetadataIndexSize => indexSize(Math.max(...rows), tagBits); + +const moduleRow = ( name: number, - namespace: number, - fieldList: number, - methodList: number, + stringSize: MetadataIndexSize, + guidSize: MetadataIndexSize, ): Buffer => Buffer.concat([ - u32(0x0010_0001), - u16(name), - u16(namespace), - u16((1 << 2) | 1), - u16(fieldList), - u16(methodList), + u16(0), + metadataIndex(name, stringSize), + metadataIndex(1, guidSize), + metadataIndex(0, guidSize), + metadataIndex(0, guidSize), ]); -const fieldRow = (name: number, signature: number): Buffer => - Buffer.concat([u16(0x0001), u16(name), u16(signature)]); +const typeRefRow = ( + name: number, + namespace: number, + stringSize: MetadataIndexSize, + resolutionScopeSize: MetadataIndexSize, +): Buffer => + Buffer.concat([ + metadataIndex(0, resolutionScopeSize), + metadataIndex(name, stringSize), + metadataIndex(namespace, stringSize), + ]); + +const typeDefRow = ({ + name, + namespace, + fieldList, + methodList, + stringSize, + typeDefOrRefSize, + fieldListSize, + methodListSize, + extendsRaw = (1 << 2) | 1, +}: { + readonly name: number; + readonly namespace: number; + readonly fieldList: number; + readonly methodList: number; + readonly stringSize: MetadataIndexSize; + readonly typeDefOrRefSize: MetadataIndexSize; + readonly fieldListSize: MetadataIndexSize; + readonly methodListSize: MetadataIndexSize; + readonly extendsRaw?: number; +}): Buffer => + Buffer.concat([ + u32(0x0010_0001), + metadataIndex(name, stringSize), + metadataIndex(namespace, stringSize), + metadataIndex(extendsRaw, typeDefOrRefSize), + metadataIndex(fieldList, fieldListSize), + metadataIndex(methodList, methodListSize), + ]); + +const fieldRow = ( + name: number, + signature: number, + stringSize: MetadataIndexSize, + blobSize: MetadataIndexSize, +): Buffer => + Buffer.concat([ + u16(0x0001), + metadataIndex(name, stringSize), + metadataIndex(signature, blobSize), + ]); interface MethodDefRowInput { readonly name: number; readonly signature: number; + readonly stringSize: MetadataIndexSize; + readonly blobSize: MetadataIndexSize; + readonly parameterSize: MetadataIndexSize; readonly rva?: number; readonly flags?: number; readonly implFlags?: number; @@ -141,6 +226,9 @@ interface MethodDefRowInput { const methodDefRow = ({ name, signature, + stringSize, + blobSize, + parameterSize, rva = 0, flags = 0x0016, implFlags = 0, @@ -149,29 +237,56 @@ const methodDefRow = ({ u32(rva), u16(implFlags), u16(flags), - u16(name), - u16(signature), - u16(0), + metadataIndex(name, stringSize), + metadataIndex(signature, blobSize), + metadataIndex(0, parameterSize), ]); -const memberRefRow = (name: number, signature: number): Buffer => - Buffer.concat([u16((1 << 3) | 1), u16(name), u16(signature)]); +const memberRefRow = ({ + name, + signature, + stringSize, + blobSize, + parentSize, + parentRaw = (1 << 3) | 1, +}: { + readonly name: number; + readonly signature: number; + readonly stringSize: MetadataIndexSize; + readonly blobSize: MetadataIndexSize; + readonly parentSize: MetadataIndexSize; + readonly parentRaw?: number; +}): Buffer => + Buffer.concat([ + metadataIndex(parentRaw, parentSize), + metadataIndex(name, stringSize), + metadataIndex(signature, blobSize), + ]); -const moduleRefRow = (name: number): Buffer => Buffer.concat([u16(name)]); +const moduleRefRow = (name: number, stringSize: MetadataIndexSize): Buffer => + Buffer.concat([metadataIndex(name, stringSize)]); const implMapRow = ( importName: number, importScope: number, mappingFlags: number, + stringSize: MetadataIndexSize, + memberForwardedSize: MetadataIndexSize, + moduleRefSize: MetadataIndexSize, + memberForwardedRaw: number, ): Buffer => Buffer.concat([ u16(mappingFlags), - u16((1 << 1) | 1), - u16(importName), - u16(importScope), + metadataIndex(memberForwardedRaw, memberForwardedSize), + metadataIndex(importName, stringSize), + metadataIndex(importScope, moduleRefSize), ]); -const assemblyRow = (name: number): Buffer => +const assemblyRow = ( + name: number, + stringSize: MetadataIndexSize, + blobSize: MetadataIndexSize, +): Buffer => Buffer.concat([ u32(0x0000_8004), u16(1), @@ -179,29 +294,71 @@ const assemblyRow = (name: number): Buffer => u16(3), u16(4), u32(0), - u16(0), - u16(name), - u16(0), + metadataIndex(0, blobSize), + metadataIndex(name, stringSize), + metadataIndex(0, stringSize), ]); -const assemblyRefRow = (name: number, keyOrToken: number): Buffer => +const assemblyRefRow = ( + name: number, + keyOrToken: number, + malformedName: boolean, + stringSize: MetadataIndexSize, + blobSize: MetadataIndexSize, +): Buffer => Buffer.concat([ u16(8), u16(0), u16(0), u16(0), u32(0), - u16(keyOrToken), - u16(name), - u16(0), - u16(0), + metadataIndex(keyOrToken, blobSize), + metadataIndex( + malformedName ? (stringSize === 2 ? 0xffff : 0xffff_ffff) : name, + stringSize, + ), + metadataIndex(0, stringSize), + metadataIndex(0, blobSize), ]); -const customAttributeRow = (value: number): Buffer => - Buffer.concat([u16((1 << 5) | 14), u16((1 << 3) | 3), u16(value)]); +const customAttributeRow = ( + value: number, + options: ManagedPeFixtureOptions, + parentIndexSize: 2 | 4, + typeIndexSize: MetadataIndexSize, + blobSize: MetadataIndexSize, +): Buffer => + Buffer.concat([ + parentIndexSize === 2 + ? u16( + options.malformedCustomAttributeRows?.includes(1) + ? 0xffff + : (1 << 5) | 14, + ) + : u32( + options.malformedCustomAttributeRows?.includes(1) + ? 0xffff_ffff + : (1 << 5) | 14, + ), + metadataIndex( + options.customAttributeTypeRaw ?? (1 << 3) | 3, + typeIndexSize, + ), + metadataIndex(value, blobSize), + ]); -const manifestResourceRow = (name: number): Buffer => - Buffer.concat([u32(0), u32(2), u16(name), u16(0)]); +const manifestResourceRow = ( + name: number, + stringSize: MetadataIndexSize, + implementationSize: MetadataIndexSize, + implementationRaw: number, +): Buffer => + Buffer.concat([ + u32(0), + u32(2), + metadataIndex(name, stringSize), + metadataIndex(implementationRaw, implementationSize), + ]); const fixedStringAttributeBlob = (value: string): Buffer => { const bytes = Buffer.from(textEncoder.encode(value)); @@ -270,11 +427,13 @@ const metadataRoot = ( const tablesStream = ( rows: ReadonlyMap, extraValidMask: bigint, + heapSizes: number, ): Buffer => { let valid = extraValidMask; for (const table of rows.keys()) valid |= 1n << BigInt(table); const header = Buffer.alloc(24); header.writeUInt8(2, 4); + header.writeUInt8(heapSizes, 6); header.writeUInt8(1, 7); header.writeBigUInt64LE(valid, 8); const counts: Buffer[] = []; @@ -292,8 +451,269 @@ const tablesStream = ( return Buffer.concat([header, ...counts, ...data]); }; +interface FixtureMetadataRowContext { + readonly options: ManagedPeFixtureOptions; + readonly methodRva: number; + readonly referenceStringIndexes: readonly number[]; + readonly indexes: { + readonly moduleName: number; + readonly assemblyName: number; + readonly attributeName: number; + readonly attributeNamespace: number; + readonly constructorName: number; + readonly typeName: number; + readonly typeNamespace: number; + readonly fieldName: number; + readonly methodName: number; + readonly pinvokeModuleName: number | null; + readonly pinvokeImportName: number | null; + readonly resourceName: number; + readonly tokenBlob: number; + readonly fieldSignature: number; + readonly methodSignature: number; + readonly constructorSignature: number; + readonly attributeBlob: number; + }; + readonly heapSizes: { + readonly strings: MetadataIndexSize; + readonly blobs: MetadataIndexSize; + readonly guids: MetadataIndexSize; + }; +} + +interface FixtureIndexWidths { + readonly table: (index: number) => MetadataIndexSize; + readonly coded: ( + tables: readonly number[], + tagBits: number, + ) => MetadataIndexSize; + readonly resolutionScope: MetadataIndexSize; + readonly typeDefOrRef: MetadataIndexSize; + readonly memberRefParent: MetadataIndexSize; + readonly hasCustomAttribute: MetadataIndexSize; + readonly customAttributeType: MetadataIndexSize; + readonly implementation: MetadataIndexSize; +} + +const repeatedRows = ( + count: number, + createRow: () => Buffer, +): readonly Buffer[] => Array.from({ length: count }, createRow); + +const customAttributeRows = ( + { + options, + indexes, + heapSizes, + }: Pick, + widths: FixtureIndexWidths, +): readonly Buffer[] => + options.targetFramework === null + ? [] + : [ + customAttributeRow( + indexes.attributeBlob, + options, + widths.hasCustomAttribute, + widths.customAttributeType, + heapSizes.blobs, + ), + ]; + +const fixtureIndexWidths = ({ + options, + referenceStringIndexes, + indexes, +}: FixtureMetadataRowContext): FixtureIndexWidths => { + const rowCounts = new Map([ + [0, options.moduleRowCount ?? 1], + [1, 1], + [2, 1], + [4, 1], + [6, 1], + [8, 0], + [10, 1], + [12, options.targetFramework === null ? 0 : 1], + ...(indexes.pinvokeModuleName === null ? [] : ([[26, 1]] as const)), + ...(indexes.pinvokeImportName === null ? [] : ([[28, 1]] as const)), + [32, options.assemblyRowCount ?? 1], + [35, referenceStringIndexes.length], + [40, 1], + ]); + const count = (table: number): number => rowCounts.get(table) ?? 0; + const table = (index: number): MetadataIndexSize => indexSize(count(index)); + const coded = ( + tables: readonly number[], + tagBits: number, + ): MetadataIndexSize => codedIndexSize(tables.map(count), tagBits); + return { + table, + coded, + resolutionScope: coded([0, 26, 35, 1], 2), + typeDefOrRef: coded([2, 1, 27], 2), + memberRefParent: coded([2, 1, 26, 6, 27], 3), + hasCustomAttribute: coded( + [ + 6, 4, 1, 2, 8, 9, 10, 0, 14, 23, 20, 17, 26, 27, 32, 35, 38, 39, 40, 42, + 44, 43, + ], + 5, + ), + customAttributeType: coded([6, 10], 3), + implementation: coded([38, 35, 39], 2), + }; +}; + +const fixtureMetadataRows = ({ + options, + methodRva, + referenceStringIndexes, + indexes, + heapSizes, +}: FixtureMetadataRowContext): ReadonlyMap => { + const widths = fixtureIndexWidths({ + options, + methodRva, + referenceStringIndexes, + indexes, + heapSizes, + }); + const rows = new Map([ + [ + 0, + repeatedRows(options.moduleRowCount ?? 1, () => + moduleRow(indexes.moduleName, heapSizes.strings, heapSizes.guids), + ), + ], + [ + 1, + [ + typeRefRow( + indexes.attributeName, + indexes.attributeNamespace, + heapSizes.strings, + widths.resolutionScope, + ), + ], + ], + [ + 2, + [ + typeDefRow({ + name: indexes.typeName, + namespace: indexes.typeNamespace, + fieldList: 1, + methodList: 1, + stringSize: heapSizes.strings, + typeDefOrRefSize: widths.typeDefOrRef, + fieldListSize: widths.table(4), + methodListSize: widths.table(6), + ...(options.extendsRaw === undefined + ? {} + : { extendsRaw: options.extendsRaw }), + }), + ], + ], + [ + 4, + [ + fieldRow( + indexes.fieldName, + indexes.fieldSignature, + heapSizes.strings, + heapSizes.blobs, + ), + ], + ], + [ + 6, + [ + methodDefRow({ + name: indexes.methodName, + signature: indexes.methodSignature, + stringSize: heapSizes.strings, + blobSize: heapSizes.blobs, + parameterSize: widths.table(8), + rva: methodRva, + flags: options.pinvoke === undefined ? 0x0016 : 0x2016, + }), + ], + ], + [ + 10, + [ + memberRefRow({ + name: indexes.constructorName, + signature: indexes.constructorSignature, + stringSize: heapSizes.strings, + blobSize: heapSizes.blobs, + parentSize: widths.memberRefParent, + ...(options.memberRefParentRaw === undefined + ? {} + : { parentRaw: options.memberRefParentRaw }), + }), + ], + ], + [12, customAttributeRows({ options, indexes, heapSizes }, widths)], + ...(indexes.pinvokeModuleName === null + ? [] + : ([ + [26, [moduleRefRow(indexes.pinvokeModuleName, heapSizes.strings)]], + ] as const)), + ...(indexes.pinvokeImportName === null + ? [] + : ([ + [ + 28, + [ + implMapRow( + indexes.pinvokeImportName, + 1, + options.pinvoke?.mappingFlags ?? 0x0344, + heapSizes.strings, + widths.coded([4, 6], 1), + widths.table(26), + options.pinvoke?.memberForwardedRaw ?? (1 << 1) | 1, + ), + ], + ], + ] as const)), + [ + 32, + repeatedRows(options.assemblyRowCount ?? 1, () => + assemblyRow(indexes.assemblyName, heapSizes.strings, heapSizes.blobs), + ), + ], + [ + 35, + referenceStringIndexes.map((name, index) => + assemblyRefRow( + name, + indexes.tokenBlob, + options.malformedAssemblyReferenceRows?.includes(index + 1) ?? false, + heapSizes.strings, + heapSizes.blobs, + ), + ), + ], + [ + 40, + [ + manifestResourceRow( + indexes.resourceName, + heapSizes.strings, + widths.implementation, + options.resourceImplementationRaw ?? 0, + ), + ], + ], + ]); + return rows; +}; + const buildManagedFixtureMetadata = ( options: ManagedPeFixtureOptions, + methodRva: number, ): Buffer => { const strings = new StringHeap(); const blobs = new BlobHeap(); @@ -329,50 +749,45 @@ const buildManagedFixtureMetadata = ( const constructorSignature = blobs.add(Buffer.from([0x20, 0x01, 0x01, 0x0e])); const attributeBlob = blobs.add( fixedStringAttributeBlob( - options.targetFramework ?? ".NETCoreApp,Version=v8.0", + options.targetFramework === null + ? ".NETCoreApp,Version=v8.0" + : (options.targetFramework ?? ".NETCoreApp,Version=v8.0"), ), ); - const rows = new Map([ - [0, [moduleRow(moduleName)]], - [1, [typeRefRow(attributeName, attributeNamespace)]], - [2, [typeDefRow(typeName, typeNamespace, 1, 1)]], - [4, [fieldRow(fieldName, fieldSignature)]], - [ - 6, - [ - methodDefRow({ - name: methodName, - signature: methodSignature, - rva: 0x2800, - flags: options.pinvoke === undefined ? 0x0016 : 0x2016, - }), - ], - ], - [10, [memberRefRow(constructorName, constructorSignature)]], - [12, [customAttributeRow(attributeBlob)]], - ...(pinvokeModuleName === null - ? [] - : ([[26, [moduleRefRow(pinvokeModuleName)]]] as const)), - ...(pinvokeImportName === null - ? [] - : ([ - [ - 28, - [ - implMapRow( - pinvokeImportName, - 1, - options.pinvoke?.mappingFlags ?? 0x0344, - ), - ], - ], - ] as const)), - [32, [assemblyRow(assemblyName)]], - [35, referenceStringIndexes.map((name) => assemblyRefRow(name, tokenBlob))], - [40, [manifestResourceRow(resourceName)]], - ]); + const stringSize = indexSize(strings.size); + const blobSize = indexSize(blobs.size); + const guidSize = indexSize(guidHeap(options.mvid).length); + const rows = fixtureMetadataRows({ + options, + methodRva, + referenceStringIndexes, + indexes: { + moduleName, + assemblyName, + attributeName, + attributeNamespace, + constructorName, + typeName, + typeNamespace, + fieldName, + methodName, + pinvokeModuleName, + pinvokeImportName, + resourceName, + tokenBlob, + fieldSignature, + methodSignature, + constructorSignature, + attributeBlob, + }, + heapSizes: { strings: stringSize, blobs: blobSize, guids: guidSize }, + }); + const heapFlags = + (stringSize === 4 ? 0x01 : 0) | + (guidSize === 4 ? 0x02 : 0) | + (blobSize === 4 ? 0x04 : 0); const metadata = metadataRoot( - tablesStream(rows, options.metadataValidMaskExtra ?? 0n), + tablesStream(rows, options.metadataValidMaskExtra ?? 0n, heapFlags), strings.toBuffer(), guidHeap(options.mvid), blobs.toBuffer(), @@ -383,14 +798,56 @@ const buildManagedFixtureMetadata = ( const buildManagedFixtureImage = ( options: ManagedPeFixtureOptions, - metadata: Buffer, + buildMetadata: (methodRva: number) => Buffer, ): Buffer => { const resourceData = options.resourceData ?? Buffer.from("resource-data"); const resourceDirectory = Buffer.concat([ u32(resourceData.length), resourceData, ]); - const image = Buffer.alloc(0x1000); + const initialMetadata = buildMetadata(0); + const resourceOffset = Math.max( + 0x0800, + Math.ceil((0x0300 + initialMetadata.length) / 0x0200) * 0x0200, + ); + const resourceRva = 0x2000 + resourceOffset - 0x0200; + const bodyOffset = Math.max( + 0x0a00, + Math.ceil((resourceOffset + resourceDirectory.length) / 0x0200) * 0x0200, + ); + const methodRva = 0x2000 + bodyOffset - 0x0200; + const metadata = buildMetadata(methodRva); + const body = + options.ilBody ?? + Buffer.from([ + 0x32, 0x02, 0x7b, 0x01, 0x00, 0x00, 0x04, 0x28, 0x01, 0x00, 0x00, 0x0a, + 0x2a, + ]); + const legacyReadyToRunOffset = 0x0900; + const metadataEnd = 0x0300 + metadata.length; + const resourceEnd = resourceOffset + resourceDirectory.length; + const bodyEnd = bodyOffset + body.length; + const overlapsReadyToRun = (start: number, end: number): boolean => + legacyReadyToRunOffset < end && legacyReadyToRunOffset + 4 > start; + const readyToRunOffset = + options.readyToRun === true + ? overlapsReadyToRun(0x0300, metadataEnd) || + overlapsReadyToRun(resourceOffset, resourceEnd) || + overlapsReadyToRun(bodyOffset, bodyEnd) + ? Math.ceil(Math.max(metadataEnd, resourceEnd, bodyEnd) / 4) * 4 + : legacyReadyToRunOffset + : null; + const rawSectionSize = + Math.ceil( + Math.max( + 0x0e00, + 0x0100 + metadata.length, + bodyOffset - 0x0200 + body.length, + readyToRunOffset === null ? 0 : readyToRunOffset - 0x0200 + 4, + ) / 0x0200, + ) * 0x0200; + const virtualSectionSize = Math.max(0x1000, rawSectionSize); + const image = Buffer.alloc(0x0200 + rawSectionSize); image.write("MZ", 0, "ascii"); image.writeUInt32LE(0x80, 0x3c); image.writeUInt32LE(0x0000_4550, 0x80); @@ -405,16 +862,19 @@ const buildManagedFixtureImage = ( image.writeUInt32LE(0x0040_0000, optional + 28); image.writeUInt32LE(0x1000, optional + 32); image.writeUInt32LE(0x200, optional + 36); - image.writeUInt32LE(0x3000, optional + 56); + image.writeUInt32LE( + Math.ceil((0x2000 + virtualSectionSize) / 0x1000) * 0x1000, + optional + 56, + ); image.writeUInt32LE(0x200, optional + 60); image.writeUInt32LE(16, optional + 92); image.writeUInt32LE(0x2000, optional + 96 + 14 * 8); image.writeUInt32LE(72, optional + 96 + 14 * 8 + 4); const section = optional + 0x00e0; image.write(".text\0\0\0", section, "ascii"); - image.writeUInt32LE(0x1000, section + 8); + image.writeUInt32LE(virtualSectionSize, section + 8); image.writeUInt32LE(0x2000, section + 12); - image.writeUInt32LE(0x0e00, section + 16); + image.writeUInt32LE(rawSectionSize, section + 16); image.writeUInt32LE(0x0200, section + 20); image.writeUInt32LE(0x6000_0020, section + 36); const cli = 0x0200; @@ -425,22 +885,16 @@ const buildManagedFixtureImage = ( image.writeUInt32LE(metadata.length, cli + 12); image.writeUInt32LE(options.cliFlags ?? 1, cli + 16); image.writeUInt32LE(0x0600_0001, cli + 20); - image.writeUInt32LE(0x2600, cli + 24); + image.writeUInt32LE(resourceRva, cli + 24); image.writeUInt32LE(resourceDirectory.length, cli + 28); - if (options.readyToRun === true) { - image.writeUInt32LE(0x2700, cli + 64); + if (readyToRunOffset !== null) { + image.writeUInt32LE(0x2000 + readyToRunOffset - 0x0200, cli + 64); image.writeUInt32LE(4, cli + 68); - image.write("RTR\0", 0x0900, "ascii"); + image.write("RTR\0", readyToRunOffset, "ascii"); } - ( - options.ilBody ?? - Buffer.from([ - 0x32, 0x02, 0x7b, 0x01, 0x00, 0x00, 0x04, 0x28, 0x01, 0x00, 0x00, 0x0a, - 0x2a, - ]) - ).copy(image, 0x0a00); + body.copy(image, bodyOffset); metadata.copy(image, 0x0300); - resourceDirectory.copy(image, 0x0800); + resourceDirectory.copy(image, resourceOffset); return image; }; @@ -448,7 +902,9 @@ const buildManagedFixtureImage = ( export const buildManagedPeFixture = ( options: ManagedPeFixtureOptions = {}, ): Buffer => - buildManagedFixtureImage(options, buildManagedFixtureMetadata(options)); + buildManagedFixtureImage(options, (methodRva) => + buildManagedFixtureMetadata(options, methodRva), + ); /** Build a syntactically valid PE32 fixture with no CLI directory. */ export const buildNativePeFixture = (): Buffer => { diff --git a/src/dotnet/ManagedPeReader.ts b/src/dotnet/ManagedPeReader.ts index 2f2f7fdba..c888a2b4b 100644 --- a/src/dotnet/ManagedPeReader.ts +++ b/src/dotnet/ManagedPeReader.ts @@ -41,6 +41,7 @@ export interface ManagedPeLayout { readonly cliDirectoryPresent: boolean; readonly cliIssue: ManagedParseIssue | null; rvaToOffset(rva: number, size: number, scope: string): number; + rvaAvailableBytes(rva: number, scope: string): number; } interface RvaMapping { @@ -355,5 +356,22 @@ export const readManagedPeLayout = (bytes: Buffer): ManagedPeLayout => { cliIssue, rvaToOffset: (rva, size, scope) => mapRva({ bytes, sections, rva, size, scope }), + rvaAvailableBytes: (rva, scope) => { + mapRva({ bytes, sections, rva, size: 1, scope }); + for (const section of sections) { + if (rva < section.virtualAddress) continue; + const within = rva - section.virtualAddress; + if (within < section.rawSize) + return Math.min( + section.rawSize - within, + bytes.length - section.rawOffset - within, + ); + } + throw managedFailure( + "invalid-directory", + scope, + `${scope} RVA is not covered by file-backed PE section data`, + ); + }, }; }; diff --git a/src/evaluation/CodexAgentEval.ts b/src/evaluation/CodexAgentEval.ts index e5fc0c7d7..e193761b3 100644 --- a/src/evaluation/CodexAgentEval.ts +++ b/src/evaluation/CodexAgentEval.ts @@ -1,3 +1,5 @@ +import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js"; + /** One REA MCP invocation observed in a Codex JSONL transcript. */ interface CodexMcpCall { readonly id: string | null; @@ -47,7 +49,7 @@ const canonicalValue = (value: unknown): unknown => { if (object === undefined) return value; return Object.fromEntries( Object.entries(object) - .sort(([left], [right]) => left.localeCompare(right)) + .sort(([left], [right]) => compareUnicodeCodePoints(left, right)) .map(([key, child]) => [key, canonicalValue(child)]), ); }; diff --git a/src/firmware/FirmwareCommand.ts b/src/firmware/FirmwareCommand.ts index 85abf068f..025596528 100644 --- a/src/firmware/FirmwareCommand.ts +++ b/src/firmware/FirmwareCommand.ts @@ -122,6 +122,7 @@ export const runFirmwareCommand = async (context: { runId, cwd: context.cwd, hostEnvironment: context.environment, + signal, env: { TMPDIR: context.cwd, OMP_NUM_THREADS: "1", diff --git a/src/firmware/FirmwareReports.test.ts b/src/firmware/FirmwareReports.test.ts index 8f658a6cc..2546c8005 100644 --- a/src/firmware/FirmwareReports.test.ts +++ b/src/firmware/FirmwareReports.test.ts @@ -74,6 +74,68 @@ describe("firmware producer boundaries", () => { ]); expect(normalized.coverage).toBe("partial"); }); +}); + +describe("Unblob multi-file extraction diagnostics", () => { + it("retains nested MultiFile extraction failures in partial coverage", () => { + const outputPath = `${context.outputRoot}/multi`; + const childTask = { + path: outputPath, + depth: 1, + blob_id: "multi-id", + is_multi_file: true, + }; + const extractionFailure = { + __typename__: "ExtractorDependencyNotFoundReport", + severity: "ERROR", + dependencies: ["sasquatch"], + }; + const multiFileReport = { + __typename__: "MultiFileReport", + id: "multi-id", + handler_name: "multipart-firmware", + name: "rootfs", + paths: [`${outputPath}/part-a`, `${outputPath}/part-b`], + extraction_reports: [extractionFailure], + }; + const parent = root(); + const normalized = normalizeUnblobReport( + [ + { + ...parent, + reports: parent.reports.filter( + (item) => item.__typename__ !== "UnknownChunkReport", + ), + subtasks: [childTask], + }, + { + task: childTask, + reports: [ + { + __typename__: "StatReport", + path: outputPath, + size: 0, + is_dir: true, + is_file: false, + is_link: false, + link_target: null, + }, + multiFileReport, + ], + subtasks: [], + }, + ], + context, + ); + + expect(normalized.coverage).toBe("partial"); + expect(normalized.diagnostics).toEqual([ + { input_path: "$output/multi", report: multiFileReport }, + ]); + }); +}); + +describe("firmware producer boundaries", () => { it("rejects unknown tasks outside the owned output and disconnected task graphs", () => { const record = root(); for (const path of [ diff --git a/src/firmware/FirmwareReports.ts b/src/firmware/FirmwareReports.ts index 67826d0ad..3a7e93d48 100644 --- a/src/firmware/FirmwareReports.ts +++ b/src/firmware/FirmwareReports.ts @@ -43,6 +43,13 @@ const stat = z.object({ is_link: z.boolean(), link_target: z.string().nullable(), }); +const multiFile = z.object({ + id: z.string(), + handler_name: z.string(), + name: z.string(), + paths: z.array(z.string()), + extraction_reports: z.array(jsonValueSchema), +}); const hashes = z.object({ sha256: digestSchema }); const region = z.object({ id: z.string(), @@ -243,6 +250,11 @@ export const normalizeUnblobReport = ( : null, }); } + if (item.__typename__ === "MultiFileReport") { + const parsed = multiFile.parse(item); + if (parsed.extraction_reports.length > 0) + diagnostics.push({ input_path: path, report: item }); + } } } const recordsByPath = new Map( diff --git a/src/generatedMcpToolCatalog.ts b/src/generatedMcpToolCatalog.ts deleted file mode 100644 index 1b50e7b84..000000000 --- a/src/generatedMcpToolCatalog.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { ToolAnnotations } from "@modelcontextprotocol/server"; -import type { - CapabilityDescriptor, - ProviderIdentity, -} from "./application/AnalysisProvider.js"; -import type { ToolKind } from "./contracts/toolContractTypes.js"; -import type { ToolEffects } from "./contracts/toolEffects.js"; - -interface GeneratedTool { - readonly name: string; - readonly analysisOperation: CapabilityDescriptor["operation"] | null; - readonly title: string; - readonly description: string; - readonly kind: ToolKind; - readonly requiresSession: boolean; - readonly inputSchema: Readonly>; - readonly outputSchema: Readonly>; - readonly annotations: ToolAnnotations; - readonly effects: ToolEffects; -} - -interface GeneratedProvider { - readonly identity: ProviderIdentity; - readonly capabilities: readonly CapabilityDescriptor[]; -} - -interface GeneratedPayload { - readonly catalog: readonly GeneratedTool[]; - readonly auxiliaryProviders: readonly GeneratedProvider[]; -} - -const GENERATED_PAYLOAD_JSON = - '{\n "auxiliaryProviders": [\n {\n "capabilities": [\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": true,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.",\n "ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."\n ],\n "operation": "inventory_artifact",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": true,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.",\n "ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."\n ],\n "operation": "inspect_artifact",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": true,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.",\n "ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."\n ],\n "operation": "extract_artifact",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": false,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.",\n "ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."\n ],\n "operation": "decode_interface_builder",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": false,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.",\n "ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."\n ],\n "operation": "inspect_keyed_archive",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.",\n "ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."\n ],\n "operation": "inspect_asset_catalog",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "reason": null\n }\n ],\n "identity": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n }\n },\n {\n "capabilities": [\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": true,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "observe_native_ui",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": true,\n "mayShowUi": true,\n "mayWriteFilesystem": true,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "capture_native_ui_scenario",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": true,\n "mayShowUi": true,\n "mayWriteFilesystem": true,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "observe_native_calls",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "inspect_macho",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "inspect_signature",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "inspect_plist",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "list_architectures",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": true,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "demangle_swift",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": false,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "Availability and textual formats depend on the installed macOS/Xcode toolchain."\n ],\n "operation": "inspect_native_dispatch_metadata",\n "provider": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n },\n "reason": null\n }\n ],\n "identity": {\n "id": "native-macos",\n "name": "macOS native inspection utilities",\n "version": null\n }\n },\n {\n "capabilities": [\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": false,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "This capability inventories PE/CLI identity only; inspect_managed_members admits metadata members, signatures, and CIL anchors.",\n "It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code."\n ],\n "operation": "inspect_managed_artifact",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": false,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "This capability decodes PE/CLI metadata members, signatures, and file-backed method bodies; decompiled C# reconstruction and cross-build member comparison are separate operations.",\n "It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code."\n ],\n "operation": "inspect_managed_members",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "reason": null\n },\n {\n "available": true,\n "effects": {\n "changesPermissions": false,\n "launchesProcess": false,\n "mayAccessNetwork": false,\n "mayShowUi": false,\n "mayWriteFilesystem": false,\n "mutatesArtifact": false,\n "requiresRoot": false\n },\n "limitations": [\n "This capability inventories managed declarations for native boundaries only; native exports, thunks, and addresses require separate native-provider evidence.",\n "It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code."\n ],\n "operation": "inspect_managed_native_boundaries",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "reason": null\n }\n ],\n "identity": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n }\n }\n ],\n "catalog": [\n {\n "analysisOperation": "annotate_native_function",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Atomically update one function name and/or entry comments, read them back, and return a refreshed complete function dossier. Ghidra changes its ephemeral session analysis database only; original executable bytes remain unchanged. Empty comment text clears that comment. Edits remain available to subsequent MCP calls until close; CLI returns the updated dossier before discarding its session. Invalid edits roll back the entire operation. Windows P0 and providers without this capability are unsupported.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "comment": "Analyst observation",\n "name": "entry",\n "procedure": "0x10100"\n }\n ],\n "properties": {\n "comment": {\n "description": "Regular entry comment; empty text clears it",\n "type": "string"\n },\n "inline_comment": {\n "description": "Inline entry comment; empty text clears it",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "procedure": {\n "description": "Value for procedure.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "annotate_native_function",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "annotations": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "comment": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "inline_comment": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "comment",\n "inline_comment"\n ],\n "type": "object"\n },\n "dossier": {\n "additionalProperties": false,\n "properties": {\n "assembly": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "basic_blocks": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "type": "string"\n },\n "start": {\n "type": "string"\n },\n "successors": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "start",\n "end",\n "successors"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "callees": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "callers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "comments": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "comment",\n "inline"\n ],\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "kind",\n "text"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "incoming_references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "call": {\n "type": "boolean"\n },\n "computed": {\n "type": "boolean"\n },\n "conditional": {\n "type": "boolean"\n },\n "data": {\n "type": "boolean"\n },\n "external": {\n "type": "boolean"\n },\n "flow": {\n "type": "boolean"\n },\n "indirect": {\n "type": "boolean"\n },\n "jump": {\n "type": "boolean"\n },\n "operand_index": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "primary": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "read": {\n "type": "boolean"\n },\n "terminal": {\n "type": "boolean"\n },\n "type": {\n "type": "string"\n },\n "write": {\n "type": "boolean"\n }\n },\n "required": [\n "available",\n "provenance",\n "type",\n "flow",\n "call",\n "jump",\n "data",\n "read",\n "write",\n "indirect",\n "computed",\n "conditional",\n "terminal",\n "primary",\n "operand_index",\n "external"\n ],\n "type": "object"\n }\n ]\n },\n "source_address": {\n "type": "string"\n },\n "source_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_address": {\n "type": "string"\n },\n "target_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "source_address",\n "target_address",\n "source_procedure",\n "target_procedure",\n "kind"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "default": [],\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "native_api": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "residual_unknowns": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "available",\n "reason",\n "residual_unknowns"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "calling_convention": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-and-stack-variables",\n "compiler-generated-control-flow",\n "pointer-arithmetic",\n "pseudocode"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "jump_tables": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "data_sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "entry_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "entry_size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "provenance": {\n "enum": [\n "ghidra-decompiler-load-table",\n "ghidra-dispatch-block-data-reference",\n "ghidra-aarch64-byte-table",\n "ghidra-aarch64-halfword-table"\n ],\n "type": "string"\n }\n },\n "required": [\n "address",\n "provenance",\n "entry_size_bytes",\n "entry_count",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "default_targets": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "target_address": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_address",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "dispatch_address": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "mappings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "case_value": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "target_address": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "case_value",\n "target_address",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "dispatch_address",\n "data_sources",\n "mappings",\n "default_targets",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "parameters": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "data_type": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-or-stack-storage",\n "compiler-generated-variable"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "role": {\n "const": "parameter",\n "type": "string"\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "storage": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type",\n "size_bytes",\n "storage",\n "confidence",\n "evidence",\n "decompiler_artifacts",\n "role"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "pseudocode": {\n "additionalProperties": false,\n "properties": {\n "classification": {\n "const": "decompiler-generated-non-source",\n "type": "string"\n },\n "compilable": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "classification",\n "compilable"\n ],\n "type": "object"\n },\n "return_type": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "data_type": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-or-stack-storage",\n "compiler-generated-variable"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "type": "null"\n },\n "ordinal": {\n "type": "null"\n },\n "role": {\n "const": "return",\n "type": "string"\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "storage": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type",\n "size_bytes",\n "storage",\n "confidence",\n "evidence",\n "decompiler_artifacts",\n "role"\n ],\n "type": "object"\n },\n "signature_source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "available",\n "provenance",\n "signature_source",\n "calling_convention",\n "return_type",\n "parameters",\n "jump_tables",\n "pseudocode",\n "decompiler_artifacts",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "native_value_flow": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "def_use": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "definition": {\n "minLength": 1,\n "type": "string"\n },\n "input_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "use": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "definition",\n "use",\n "input_index"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "effects": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "memory_read",\n "memory_write",\n "conditional_branch",\n "indirect_branch",\n "direct_call",\n "indirect_call"\n ],\n "type": "string"\n },\n "operand_input": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "value_input": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "operation",\n "kind",\n "operand_input",\n "value_input"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "known_omitted_edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "known_omitted_inputs": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "omitted_operations_lower_bound": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "operations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "block_membership": {\n "default": "unavailable",\n "enum": [\n "member",\n "detached",\n "unavailable"\n ],\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "inputs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "constant_hex": {\n "anyOf": [\n {\n "pattern": "^[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "constant",\n "address",\n "register",\n "unique",\n "other"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "size_bytes",\n "location",\n "constant_hex"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "is_dead": {\n "type": "boolean"\n },\n "opcode": {\n "minLength": 1,\n "type": "string"\n },\n "output": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "constant_hex": {\n "anyOf": [\n {\n "pattern": "^[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "constant",\n "address",\n "register",\n "unique",\n "other"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "size_bytes",\n "location",\n "constant_hex"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "id",\n "address",\n "sequence",\n "opcode",\n "is_dead",\n "block_membership",\n "inputs",\n "output"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "parameter_uses": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "input_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "use": {\n "type": "string"\n }\n },\n "required": [\n "ordinal",\n "use",\n "input_index"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "parameters": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "data_type": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provenance": {\n "const": "ghidra-high-pcode",\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "available",\n "provenance",\n "operations",\n "def_use",\n "effects",\n "parameters",\n "parameter_uses",\n "truncated",\n "omitted_operations_lower_bound",\n "known_omitted_inputs",\n "known_omitted_edges",\n "limitations"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "outgoing_references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "call": {\n "type": "boolean"\n },\n "computed": {\n "type": "boolean"\n },\n "conditional": {\n "type": "boolean"\n },\n "data": {\n "type": "boolean"\n },\n "external": {\n "type": "boolean"\n },\n "flow": {\n "type": "boolean"\n },\n "indirect": {\n "type": "boolean"\n },\n "jump": {\n "type": "boolean"\n },\n "operand_index": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "primary": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "read": {\n "type": "boolean"\n },\n "terminal": {\n "type": "boolean"\n },\n "type": {\n "type": "string"\n },\n "write": {\n "type": "boolean"\n }\n },\n "required": [\n "available",\n "provenance",\n "type",\n "flow",\n "call",\n "jump",\n "data",\n "read",\n "write",\n "indirect",\n "computed",\n "conditional",\n "terminal",\n "primary",\n "operand_index",\n "external"\n ],\n "type": "object"\n }\n ]\n },\n "source_address": {\n "type": "string"\n },\n "source_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_address": {\n "type": "string"\n },\n "target_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "source_address",\n "target_address",\n "source_procedure",\n "target_procedure",\n "kind"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "locals": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "description": {\n "type": "string"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "description",\n "provenance"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "signature": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body",\n "signature",\n "locals"\n ],\n "type": "object"\n },\n "pseudocode": {\n "type": "string"\n },\n "referenced_names": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "source_address": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value",\n "source_address"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "referenced_strings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "source_address": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value",\n "source_address"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "procedure",\n "pseudocode",\n "assembly",\n "comments",\n "callers",\n "callees",\n "incoming_references",\n "outgoing_references",\n "referenced_strings",\n "referenced_names",\n "basic_blocks",\n "native_api",\n "native_value_flow",\n "limitations"\n ],\n "type": "object"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "persists_after_close": {\n "const": false,\n "type": "boolean"\n },\n "scope": {\n "const": "session-analysis-database",\n "type": "string"\n },\n "source_bytes_modified": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "scope",\n "source_bytes_modified",\n "persists_after_close"\n ],\n "type": "object"\n }\n },\n "required": [\n "annotations",\n "dossier",\n "effects"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Annotate Native Function"\n },\n {\n "analysisOperation": "inspect_native_load_image",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Verify the provider\'s loaded DOS MZ or explicitly selected COM image against its immutable target snapshot: complete original/modified source-byte digests, header/module file mappings and memory digests, relocations and external entry. Returns measured observations and independent checks with mismatch coordinates. COM verification includes its imposed entry register context and does not claim PSP/runtime coverage. Overlay and uninitialized coverage remain explicit; other formats return unsupported. Does not execute or modify the target.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "inspect_native_load_image",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "observations": {\n "additionalProperties": false,\n "properties": {\n "compiler_spec_id": {\n "minLength": 1,\n "type": "string"\n },\n "default_address_space": {\n "minLength": 1,\n "type": "string"\n },\n "entry_context": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "registers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "value_hex": {\n "anyOf": [\n {\n "pattern": "^0x[a-f0-9]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "value_hex"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "address",\n "registers"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "entry_points": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "executable_format": {\n "minLength": 1,\n "type": "string"\n },\n "image_base": {\n "minLength": 1,\n "type": "string"\n },\n "language_id": {\n "minLength": 1,\n "type": "string"\n },\n "mappings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address_space": {\n "minLength": 1,\n "type": "string"\n },\n "block": {\n "type": "string"\n },\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "initialized": {\n "type": "boolean"\n },\n "length": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "loaded": {\n "type": "boolean"\n },\n "overlay": {\n "type": "boolean"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "source_file_index": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "block",\n "start",\n "end",\n "address_space",\n "initialized",\n "loaded",\n "overlay",\n "length",\n "source_file_index",\n "file_offset",\n "sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "metadata_recovery": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "rehydrated-pointer-candidates-and-committed-instance-types",\n "type": "string"\n },\n "frozen_object_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "frozen_objects_annotated": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "frozen_object_candidates",\n "frozen_objects_annotated",\n "basis"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "derived_memory": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "file_offset": {\n "type": "null"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "address",\n "size_bytes",\n "sha256",\n "file_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "diagnostics": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "discovery": {\n "anyOf": [\n {\n "enum": [\n "symbol",\n "signature-heuristic"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "format": {\n "const": "dotnet-nativeaot",\n "type": "string"\n },\n "format_major": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "format_minor": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "header_address": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "method_tables": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "not_applicable"\n ],\n "type": "string"\n },\n "types": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "type"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "format",\n "status",\n "reason",\n "analysis_artifact_sha256",\n "header_address",\n "discovery",\n "format_major",\n "format_minor",\n "method_tables",\n "types",\n "derived_memory",\n "coverage",\n "diagnostics",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relocations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "memory_bytes_hex": {\n "anyOf": [\n {\n "pattern": "^(?:[a-f0-9]{2})*$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "offset": {\n "anyOf": [\n {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "original_bytes_hex": {\n "anyOf": [\n {\n "pattern": "^(?:[a-f0-9]{2})*$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "segment": {\n "anyOf": [\n {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "minLength": 1,\n "type": "string"\n },\n "type": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "values": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "type": "array"\n }\n },\n "required": [\n "address",\n "segment",\n "offset",\n "status",\n "type",\n "values",\n "original_bytes_hex",\n "memory_bytes_hex"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "source_files": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "modified_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "original_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "size",\n "original_sha256",\n "modified_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "executable_format",\n "language_id",\n "compiler_spec_id",\n "image_base",\n "default_address_space",\n "source_files",\n "mappings",\n "relocations",\n "entry_points",\n "entry_context"\n ],\n "type": "object"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unsupported",\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason",\n "observations",\n "limitations"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "checks": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "expected": {\n "$ref": "#/$defs/__schema0"\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "matched": {\n "type": "boolean"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "observed": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "name",\n "matched",\n "expected",\n "observed",\n "file_offset",\n "address"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "entry": {\n "additionalProperties": false,\n "properties": {\n "linear_address": {\n "minLength": 1,\n "type": "string"\n },\n "offset": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "relative_segment": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "relative_segment",\n "offset",\n "linear_address"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "dos-mz",\n "dos-com"\n ],\n "type": "string"\n },\n "header_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "load_segment": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "module_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "observations": {\n "additionalProperties": false,\n "properties": {\n "compiler_spec_id": {\n "minLength": 1,\n "type": "string"\n },\n "default_address_space": {\n "minLength": 1,\n "type": "string"\n },\n "entry_context": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "registers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "value_hex": {\n "anyOf": [\n {\n "pattern": "^0x[a-f0-9]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "value_hex"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "address",\n "registers"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "entry_points": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "executable_format": {\n "minLength": 1,\n "type": "string"\n },\n "image_base": {\n "minLength": 1,\n "type": "string"\n },\n "language_id": {\n "minLength": 1,\n "type": "string"\n },\n "mappings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address_space": {\n "minLength": 1,\n "type": "string"\n },\n "block": {\n "type": "string"\n },\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "initialized": {\n "type": "boolean"\n },\n "length": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "loaded": {\n "type": "boolean"\n },\n "overlay": {\n "type": "boolean"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "source_file_index": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "block",\n "start",\n "end",\n "address_space",\n "initialized",\n "loaded",\n "overlay",\n "length",\n "source_file_index",\n "file_offset",\n "sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "metadata_recovery": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "rehydrated-pointer-candidates-and-committed-instance-types",\n "type": "string"\n },\n "frozen_object_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "frozen_objects_annotated": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "frozen_object_candidates",\n "frozen_objects_annotated",\n "basis"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "derived_memory": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "file_offset": {\n "type": "null"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "address",\n "size_bytes",\n "sha256",\n "file_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "diagnostics": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "discovery": {\n "anyOf": [\n {\n "enum": [\n "symbol",\n "signature-heuristic"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "format": {\n "const": "dotnet-nativeaot",\n "type": "string"\n },\n "format_major": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "format_minor": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "header_address": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "method_tables": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "not_applicable"\n ],\n "type": "string"\n },\n "types": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "type"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "format",\n "status",\n "reason",\n "analysis_artifact_sha256",\n "header_address",\n "discovery",\n "format_major",\n "format_minor",\n "method_tables",\n "types",\n "derived_memory",\n "coverage",\n "diagnostics",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relocations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "memory_bytes_hex": {\n "anyOf": [\n {\n "pattern": "^(?:[a-f0-9]{2})*$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "offset": {\n "anyOf": [\n {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "original_bytes_hex": {\n "anyOf": [\n {\n "pattern": "^(?:[a-f0-9]{2})*$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "segment": {\n "anyOf": [\n {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "minLength": 1,\n "type": "string"\n },\n "type": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "values": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "type": "array"\n }\n },\n "required": [\n "address",\n "segment",\n "offset",\n "status",\n "type",\n "values",\n "original_bytes_hex",\n "memory_bytes_hex"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "source_files": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "modified_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "original_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "size",\n "original_sha256",\n "modified_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "executable_format",\n "language_id",\n "compiler_spec_id",\n "image_base",\n "default_address_space",\n "source_files",\n "mappings",\n "relocations",\n "entry_points",\n "entry_context"\n ],\n "type": "object"\n },\n "overlay_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "verified",\n "mismatch"\n ],\n "type": "string"\n },\n "target_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "status",\n "format",\n "target_sha256",\n "load_segment",\n "header_bytes",\n "module_bytes",\n "overlay_bytes",\n "entry",\n "observations",\n "checks",\n "limitations"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Native Load Image"\n },\n {\n "analysisOperation": "inspect_native_data_type",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Inspect one recovered type by exact database category path or defined typed data address. Returns observed size, alignment, packing, fields, bitfields, enum values, and child type identities; source-level authority and flexible-tail semantics remain unknown unless substantiated.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "type": "/MyStruct"\n }\n ],\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "document": {\n "description": "Exact provider document or program identity.",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "inspect_native_data_type",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "alignment_bytes": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "array_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "array_stride_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "fields": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "bit_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "bit_size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "flexible_tail": {\n "const": "unknown",\n "type": "string"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "offset_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "observed",\n "unsupported"\n ],\n "type": "string"\n },\n "type_id": {\n "type": "string"\n }\n },\n "required": [\n "ordinal",\n "name",\n "offset_bytes",\n "size_bytes",\n "type_id",\n "bit_size",\n "bit_offset",\n "status",\n "flexible_tail"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "struct",\n "union",\n "enum",\n "pointer",\n "array",\n "typedef",\n "scalar",\n "unsupported",\n "unavailable"\n ],\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "members": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "metadata_recovery": {\n "additionalProperties": false,\n "properties": {\n "base_size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "derived_memory": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "file_offset": {\n "type": "null"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "address",\n "size_bytes",\n "sha256",\n "file_offset"\n ],\n "type": "object"\n },\n "diagnostics": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "format": {\n "const": "dotnet-nativeaot",\n "type": "string"\n },\n "format_major": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "format_minor": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "header_address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "interfaces": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "type"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "method_table_address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "name_origin": {\n "enum": [\n "generated",\n "inferred"\n ],\n "type": "string"\n },\n "original_name": {\n "type": "null"\n },\n "related_type": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "type"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n },\n "virtual_slots": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "method-table-pointer",\n "type": "string"\n },\n "procedure_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "slot": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "slot_address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "target_address": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "slot",\n "slot_address",\n "target_address",\n "procedure_name",\n "basis"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "format",\n "status",\n "header_address",\n "format_major",\n "format_minor",\n "method_table_address",\n "name_origin",\n "original_name",\n "base_size_bytes",\n "related_type",\n "interfaces",\n "virtual_slots",\n "derived_memory",\n "diagnostics",\n "limitations"\n ],\n "type": "object"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "packing_enabled": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "referenced_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "source": {\n "const": "analysis-database",\n "type": "string"\n },\n "source_archive": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "available",\n "unavailable"\n ],\n "type": "string"\n },\n "total_fields": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "reason",\n "id",\n "name",\n "kind",\n "source",\n "source_archive",\n "address",\n "size_bytes",\n "alignment_bytes",\n "packing_enabled",\n "referenced_type",\n "array_count",\n "array_stride_bytes",\n "fields",\n "members",\n "total_fields",\n "truncated",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Native Data Type"\n },\n {\n "analysisOperation": "inspect_native_instruction",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Inspect one exact instruction address: decoded bytes, mnemonic, ordered operand tokens, flow and typed references. Memory addressing decomposition is unavailable when the provider supplies only tokens. Data, interior instruction addresses and undecodable bytes are distinct outcomes.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "document": {\n "description": "Exact provider document or program identity.",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "inspect_native_instruction",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "architecture": {\n "type": "string"\n },\n "bytes": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "flow": {\n "additionalProperties": false,\n "properties": {\n "computed": {\n "type": "boolean"\n },\n "conditional": {\n "type": "boolean"\n },\n "direct_destinations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "enum": [\n "call",\n "jump",\n "return",\n "fallthrough",\n "terminal",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "conditional",\n "computed",\n "direct_destinations"\n ],\n "type": "object"\n },\n "length": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "mnemonic": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "mode": {\n "type": "string"\n },\n "operands": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "components": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "bit_width": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "register",\n "immediate",\n "address",\n "unknown"\n ],\n "type": "string"\n },\n "text": {\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "text",\n "value",\n "bit_width"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "memory_addressing": {\n "const": "unavailable",\n "type": "string"\n },\n "provider_type": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "raw": {\n "type": "string"\n }\n },\n "required": [\n "index",\n "raw",\n "provider_type",\n "components",\n "memory_addressing"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "procedure": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "raw_disassembly": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "call": {\n "type": "boolean"\n },\n "computed": {\n "type": "boolean"\n },\n "indirect": {\n "type": "boolean"\n },\n "jump": {\n "type": "boolean"\n },\n "operand_index": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "target_address": {\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "target_address",\n "type",\n "call",\n "jump",\n "indirect",\n "computed",\n "operand_index"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "decoded",\n "not-instruction-boundary",\n "data",\n "undecodable",\n "outside-memory"\n ],\n "type": "string"\n }\n },\n "required": [\n "address",\n "status",\n "procedure",\n "architecture",\n "mode",\n "bytes",\n "length",\n "mnemonic",\n "raw_disassembly",\n "operands",\n "flow",\n "references",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Native Instruction"\n },\n {\n "analysisOperation": "resolve_native_call_targets",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Resolve one explicit static call site using typed provider call references. Preserves ambiguous targets as candidates and unresolved computed calls as unknown; does not execute code or traverse a call graph.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "document": {\n "description": "Exact provider document or program identity.",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "resolve_native_call_targets",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "call_site": {\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "mechanism": {\n "enum": [\n "direct",\n "computed",\n "indirect",\n "unavailable"\n ],\n "type": "string"\n },\n "procedure": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "direct",\n "resolved-indirect",\n "ambiguous",\n "unresolved",\n "not-call",\n "unavailable"\n ],\n "type": "string"\n },\n "targets": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "basis": {\n "const": "provider-reference",\n "type": "string"\n },\n "procedure": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "call": {\n "type": "boolean"\n },\n "computed": {\n "type": "boolean"\n },\n "indirect": {\n "type": "boolean"\n },\n "jump": {\n "type": "boolean"\n },\n "operand_index": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "target_address": {\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "target_address",\n "type",\n "call",\n "jump",\n "indirect",\n "computed",\n "operand_index"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "direct",\n "resolved-indirect",\n "candidate"\n ],\n "type": "string"\n }\n },\n "required": [\n "address",\n "procedure",\n "status",\n "basis",\n "references"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "call_site",\n "procedure",\n "status",\n "mechanism",\n "targets",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Resolve Native Call Targets"\n },\n {\n "analysisOperation": "address_name",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Resolve the primary analyzed name at a code or data address. Headless providers require an explicit address; GUI providers may default to their current cursor. Null means the provider has no primary name at that address.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "address_name",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Address Name"\n },\n {\n "analysisOperation": "comment",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Read the regular analysis comment at an address, defaulting to the current cursor. This is read-only and returns null when no comment exists; use set_comment to persist a finding.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "comment",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Comment"\n },\n {\n "analysisOperation": "current_address",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return Hopper\'s current cursor address for the selected document. Use only for interactive navigation state; prefer explicit addresses in reproducible investigations.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "current_address",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Current Address"\n },\n {\n "analysisOperation": "current_procedure",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return the analyzed procedure containing Hopper\'s current cursor. This uses GUI cursor state and is not a procedure-name lookup.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "current_procedure",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Current Procedure"\n },\n {\n "analysisOperation": "current_document",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return the document currently selected by REA\'s Hopper bridge.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "current_document",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Current Document"\n },\n {\n "analysisOperation": "goto_address",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Move Hopper\'s GUI cursor to a hexadecimal address and return the resolved address. This changes navigation state but not analysis data; use explicit-address tools for headless workflows.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": true,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "goto_address",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Goto Address"\n },\n {\n "analysisOperation": "inline_comment",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Read the inline instruction comment at an address, defaulting to the current cursor. Returns null when absent; use set_inline_comment to write one.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "inline_comment",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inline Comment"\n },\n {\n "analysisOperation": "list_bookmarks",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "List every bookmark in the selected Hopper document as address and name pairs. Use bookmarks as analyst-authored navigation aids; this does not discover code references.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "list_bookmarks",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Bookmarks"\n },\n {\n "analysisOperation": "list_documents",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "List provider program or document identities. Hopper may expose several documents; a Ghidra headless session contains exactly its one imported Program.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "list_documents",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Documents"\n },\n {\n "analysisOperation": "list_names",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "List every analyzed memory and external symbol as address/value pairs. Provider metadata distinguishes Ghidra primary, dynamic, external, type, and source facts when available.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "list_names",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target"\n ],\n "type": "object"\n },\n "string": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "encoding": {\n "type": "string"\n },\n "termination": {\n "enum": [\n "missing",\n "present_or_not_required"\n ],\n "type": "string"\n }\n },\n "required": [\n "encoding",\n "termination",\n "byte_length"\n ],\n "type": "object"\n },\n "symbol": {\n "additionalProperties": false,\n "properties": {\n "dynamic": {\n "type": "boolean"\n },\n "external": {\n "type": "boolean"\n },\n "primary": {\n "type": "boolean"\n },\n "source": {\n "enum": [\n "default",\n "analysis",\n "ai",\n "imported",\n "user_defined"\n ],\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "primary",\n "dynamic",\n "external",\n "type",\n "source"\n ],\n "type": "object"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Names"\n },\n {\n "analysisOperation": "list_procedures",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "List every analyzed procedure as address/value pairs after provider analysis. Ghidra metadata distinguishes thunks and external functions; use returned addresses in later function operations.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "list_procedures",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target"\n ],\n "type": "object"\n },\n "string": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "encoding": {\n "type": "string"\n },\n "termination": {\n "enum": [\n "missing",\n "present_or_not_required"\n ],\n "type": "string"\n }\n },\n "required": [\n "encoding",\n "termination",\n "byte_length"\n ],\n "type": "object"\n },\n "symbol": {\n "additionalProperties": false,\n "properties": {\n "dynamic": {\n "type": "boolean"\n },\n "external": {\n "type": "boolean"\n },\n "primary": {\n "type": "boolean"\n },\n "source": {\n "enum": [\n "default",\n "analysis",\n "ai",\n "imported",\n "user_defined"\n ],\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "primary",\n "dynamic",\n "external",\n "type",\n "source"\n ],\n "type": "object"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Procedures"\n },\n {\n "analysisOperation": "list_segments",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "List segments or memory blocks using exclusive end addresses. Ghidra reports block permissions, address space, image base, initialization, and overlay facts; Hopper marks unavailable permissions explicitly.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "list_segments",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address_space": {\n "type": "string"\n },\n "end": {\n "type": "string"\n },\n "executable": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "image_base": {\n "type": "string"\n },\n "initialized": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "overlay": {\n "type": "boolean"\n },\n "permissions": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "source": {\n "const": "ghidra-memory-block",\n "type": "string"\n }\n },\n "required": [\n "available",\n "source"\n ],\n "type": "object"\n }\n ]\n },\n "provenance": {\n "enum": [\n "hopper-public-python-api",\n "ghidra-memory-block"\n ],\n "type": "string"\n },\n "readable": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "sections": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address_space": {\n "type": "string"\n },\n "end": {\n "type": "string"\n },\n "executable": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "image_base": {\n "type": "string"\n },\n "initialized": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "overlay": {\n "type": "boolean"\n },\n "permissions": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "source": {\n "const": "ghidra-memory-block",\n "type": "string"\n }\n },\n "required": [\n "available",\n "source"\n ],\n "type": "object"\n }\n ]\n },\n "provenance": {\n "enum": [\n "hopper-public-python-api",\n "ghidra-memory-block"\n ],\n "type": "string"\n },\n "readable": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "start": {\n "type": "string"\n },\n "writable": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "start",\n "end",\n "readable",\n "writable",\n "executable",\n "permissions",\n "provenance"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "start": {\n "type": "string"\n },\n "writable": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "start",\n "end",\n "readable",\n "writable",\n "executable",\n "permissions",\n "provenance",\n "sections"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Segments"\n },\n {\n "analysisOperation": "list_strings",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "List every provider-defined string, or filter to one address, as address/value pairs. Ghidra also reports encoding, terminator status, and byte length.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "list_strings",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "string": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "encoding": {\n "minLength": 1,\n "type": "string"\n },\n "termination": {\n "enum": [\n "missing",\n "present_or_not_required"\n ],\n "type": "string"\n }\n },\n "required": [\n "encoding",\n "termination",\n "byte_length"\n ],\n "type": "object"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Strings"\n },\n {\n "analysisOperation": "next_address",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return the next analyzed object address after an explicit address or current cursor. This is a navigation primitive, not instruction-flow or CFG analysis.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "next_address",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Next Address"\n },\n {\n "analysisOperation": "prev_address",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return the previous analyzed instruction start before an explicit address or current cursor. This is a navigation primitive and may fail at document boundaries.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "prev_address",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Prev Address"\n },\n {\n "analysisOperation": "procedure_address",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Resolve an unambiguous procedure symbol name or provider-normalized address to its canonical entry address. External address spaces remain explicit.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "procedure_address",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Procedure Address"\n },\n {\n "analysisOperation": "procedure_assembly",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return assembly for one analyzed procedure identified by symbol or hexadecimal address. Use when pseudocode loses calling-convention or instruction-level detail; output is currently returned as one unpaginated string.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "procedure_assembly",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Procedure Assembly"\n },\n {\n "analysisOperation": "procedure_callees",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return the provider\'s resolved direct callees for one procedure identified by symbol or address. Unresolved indirect calls may be absent; use analyze_function and typed references to preserve available edge uncertainty.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "procedure_callees",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Procedure Callees"\n },\n {\n "analysisOperation": "procedure_callers",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return the provider\'s resolved direct callers for one procedure identified by symbol or address. Results reflect completed static analysis and may omit unresolved indirect references.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "procedure_callers",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Procedure Callers"\n },\n {\n "analysisOperation": "procedure_info",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return provider metadata for one procedure identified by symbol or address: entrypoint, signature, locals, size, block count, and complete inclusive body ranges when observed. Body byte count and enclosing span remain distinct; unavailable extent is explicit.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "procedure_info",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "basicblock_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "entrypoint": {\n "type": "string"\n },\n "length": {\n "minimum": 0,\n "type": "number"\n },\n "locals": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "description": {\n "type": "string"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "description",\n "provenance"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "signature": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "entrypoint",\n "basicblock_count",\n "length",\n "signature",\n "locals",\n "classification",\n "body"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Procedure Info"\n },\n {\n "analysisOperation": "read_function_instructions",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return every raw instruction for one analyzed procedure. Instruction text is provider-specific.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "read_function_instructions",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "instructions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n }\n },\n "required": [\n "procedure",\n "instructions",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Read Function Instructions"\n },\n {\n "analysisOperation": "read_bytes",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Read analyzed bytes from one provider-normalized virtual address. The hexadecimal payload reports the exact returned length; incomplete reads remain explicit and unsupported provider APIs return typed capability unavailability.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000",\n "length": 16\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "length": {\n "default": 256,\n "description": "Value for length.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "read_bytes",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "bytes_hex": {\n "pattern": "^(?:[a-f0-9]{2})*$",\n "type": "string"\n },\n "complete": {\n "type": "boolean"\n },\n "requested_bytes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "returned_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "address",\n "requested_bytes",\n "returned_bytes",\n "bytes_hex",\n "complete"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Read Bytes"\n },\n {\n "analysisOperation": "address_to_file_offset",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Map one provider-normalized virtual address to its original nonnegative file offset. Unmapped addresses fail explicitly, and providers without an authoritative mapping API return typed capability unavailability.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "address_to_file_offset",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "file_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Address To File Offset"\n },\n {\n "analysisOperation": "procedure_references",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return every raw incoming or outgoing reference edge for one procedure. Endpoint procedures are resolved only from provider containment; Ghidra preserves observed reference kinds while providers without kind authority mark them unavailable.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "direction": "outgoing",\n "procedure": "main"\n }\n ],\n "properties": {\n "direction": {\n "default": "outgoing",\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "incoming",\n "outgoing"\n ],\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "procedure_references",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "direction": {\n "enum": [\n "incoming",\n "outgoing"\n ],\n "type": "string"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n "reference_kinds_available": {\n "type": "boolean"\n },\n "references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "call": {\n "type": "boolean"\n },\n "computed": {\n "type": "boolean"\n },\n "conditional": {\n "type": "boolean"\n },\n "data": {\n "type": "boolean"\n },\n "external": {\n "type": "boolean"\n },\n "flow": {\n "type": "boolean"\n },\n "indirect": {\n "type": "boolean"\n },\n "jump": {\n "type": "boolean"\n },\n "operand_index": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "primary": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "read": {\n "type": "boolean"\n },\n "terminal": {\n "type": "boolean"\n },\n "type": {\n "type": "string"\n },\n "write": {\n "type": "boolean"\n }\n },\n "required": [\n "available",\n "provenance",\n "type",\n "flow",\n "call",\n "jump",\n "data",\n "read",\n "write",\n "indirect",\n "computed",\n "conditional",\n "terminal",\n "primary",\n "operand_index",\n "external"\n ],\n "type": "object"\n }\n ]\n },\n "source_address": {\n "type": "string"\n },\n "source_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_address": {\n "type": "string"\n },\n "target_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "source_address",\n "target_address",\n "source_procedure",\n "target_procedure",\n "kind"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "unresolved_calls": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "procedure",\n "direction",\n "unresolved_calls",\n "references"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Procedure References"\n },\n {\n "analysisOperation": "procedure_pseudo_code",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Decompile one analyzed procedure by symbol name or provider-normalized address. Returns provider-specific pseudocode, never original source or cross-provider text equivalence, and may return null; request procedure_assembly when instruction precision matters.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "procedure": {\n "description": "The procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "procedure_pseudo_code",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Procedure Pseudo Code"\n },\n {\n "analysisOperation": "resolve_containing_procedure",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Resolve an arbitrary address, including an interior instruction or exact external entry, to its provider-analyzed containing procedure. A negative result distinguishes outside segments from not in a procedure and is never guessed from nearby symbols.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "resolve_containing_procedure",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "found": {\n "const": true,\n "type": "boolean"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n "query_address": {\n "type": "string"\n }\n },\n "required": [\n "query_address",\n "found",\n "procedure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "found": {\n "const": false,\n "type": "boolean"\n },\n "procedure": {\n "type": "null"\n },\n "query_address": {\n "type": "string"\n },\n "reason": {\n "enum": [\n "outside_segments",\n "not_in_procedure"\n ],\n "type": "string"\n }\n },\n "required": [\n "query_address",\n "found",\n "procedure",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Resolve Containing Procedure"\n },\n {\n "analysisOperation": "search_procedures",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Search every analyzed procedure name using literal matching by default or regex when requested. Results are deterministic and complete.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "case_sensitive": false,\n "mode": "literal",\n "pattern": "main"\n }\n ],\n "properties": {\n "case_sensitive": {\n "default": false,\n "description": "Whether to match case",\n "type": "boolean"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "mode": {\n "default": "literal",\n "description": "Operation mode that selects the requested behavior.",\n "enum": [\n "literal",\n "regex"\n ],\n "type": "string"\n },\n "pattern": {\n "description": "The literal text or regex pattern to search for",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "pattern"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "search_procedures",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Search Procedures"\n },\n {\n "analysisOperation": "search_strings",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Search every analyzed string using literal matching by default or regex when requested. Results are deterministic and complete.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "case_sensitive": false,\n "mode": "literal",\n "pattern": "authorization failed"\n }\n ],\n "properties": {\n "case_sensitive": {\n "default": false,\n "description": "Whether to match case",\n "type": "boolean"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "mode": {\n "default": "literal",\n "description": "Operation mode that selects the requested behavior.",\n "enum": [\n "literal",\n "regex"\n ],\n "type": "string"\n },\n "pattern": {\n "description": "The literal text or regex pattern to search for",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "pattern"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "search_strings",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Search Strings"\n },\n {\n "analysisOperation": "set_address_name",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Assign an analyst name to one hexadecimal address and report Hopper\'s boolean result. This mutates analysis metadata.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000",\n "name": "entry"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n }\n },\n "required": [\n "address",\n "name"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "set_address_name",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "boolean"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Set Address Name"\n },\n {\n "analysisOperation": "set_addresses_names",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Assign analyst names to multiple addresses in one call and return per-address success booleans. This mutates analysis metadata; verify failures individually.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "names": {\n "0x1000": "entry"\n }\n }\n ],\n "properties": {\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "names": {\n "additionalProperties": {\n "type": "string"\n },\n "description": "Value for names.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "names"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "set_addresses_names",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": {\n "type": "boolean"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Set Addresses Names"\n },\n {\n "analysisOperation": "set_bookmark",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Create or replace a bookmark at a hexadecimal address and report success. This mutates navigation metadata; bookmarks are analyst-authored navigation aids, not binary evidence.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "set_bookmark",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "boolean"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Set Bookmark"\n },\n {\n "analysisOperation": "set_comment",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Write a regular analysis comment at a hexadecimal address and return whether readback matched. This mutates the Hopper document; use comments to record evidence IDs or reasoning.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000",\n "comment": "validated entry point"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "comment": {\n "description": "Exact analyst comment text to write.",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "required": [\n "address",\n "comment"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "set_comment",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "boolean"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Set Comment"\n },\n {\n "analysisOperation": "set_current_document",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Select an already-open Hopper document by exact document name. This changes subsequent default-document routing; list_documents can supply names when needed, while explicit document inputs keep calls reproducible.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": true,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "document": "fixture"\n }\n ],\n "properties": {\n "document": {\n "description": "Exact provider document or program identity.",\n "type": "string"\n }\n },\n "required": [\n "document"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "set_current_document",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "string"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Set Current Document"\n },\n {\n "analysisOperation": "set_inline_comment",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Write an inline instruction comment at a hexadecimal address and return whether readback matched. This mutates analysis metadata.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000",\n "comment": "calls parser"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "comment": {\n "description": "Exact analyst comment text to write.",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "required": [\n "address",\n "comment"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "set_inline_comment",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "boolean"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Set Inline Comment"\n },\n {\n "analysisOperation": "unset_bookmark",\n "annotations": {\n "destructiveHint": true,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Remove the bookmark at a hexadecimal address and return whether it is absent. This mutates navigation metadata and does not alter binary bytes.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": true,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "unset_bookmark",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "type": "boolean"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Unset Bookmark"\n },\n {\n "analysisOperation": "xrefs",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return analyzed references to a code or data address. Hopper may default to its current cursor; headless providers such as Ghidra require an explicit address. Use to connect strings, globals, selectors, and functions; bare addresses are untyped and indirect references may be incomplete.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized address; default memory uses 0x-prefixed hexadecimal",\n "type": "string"\n },\n "document": {\n "description": "The document name",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "official-proxy",\n "name": "xrefs",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Xrefs"\n },\n {\n "analysisOperation": "inspect_native_dispatch_metadata",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Inspect the bound provider\'s name inventory for Objective-C class/method symbols and Swift mangled symbols, returning at most max_records decoded names. Includes target SHA-256 when available, provider identity/version, analysis-profile digest, facet coverage, and per-record Evidence. This is symbol-level evidence, not runtime metadata: ivar layouts, protocol conformances, witness/class tables, and relative pointers are explicitly marked unsupported unless a provider supplies those decoders. The provider controls how its name inventory is acquired.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "max_records": 5000\n }\n ],\n "properties": {\n "max_records": {\n "default": 5000,\n "description": "Maximum permitted records for this operation.",\n "maximum": 20000,\n "minimum": 1,\n "type": "integer"\n }\n },\n "type": "object"\n },\n "kind": "enhanced",\n "name": "inspect_native_dispatch_metadata",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile_digest": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "decoded": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "examined": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "facet": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "unsupported",\n "not_requested"\n ],\n "type": "string"\n }\n },\n "required": [\n "facet",\n "status",\n "reason",\n "examined",\n "decoded"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "db_save_result": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "database_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "error": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "operation": {\n "enum": [\n "save",\n "readback",\n "open_database",\n "close_database",\n "export",\n "import"\n ],\n "type": "string"\n },\n "preserved_bookmarks": {\n "default": 0,\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "preserved_comments": {\n "default": 0,\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "preserved_names": {\n "default": 0,\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "succeeded": {\n "type": "boolean"\n }\n },\n "required": [\n "operation",\n "succeeded",\n "preserved_names",\n "preserved_comments",\n "preserved_bookmarks",\n "database_path",\n "error"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "objc_categories": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "class_methods": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "class_name": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "class_source": {\n "enum": [\n "local",\n "external",\n "unresolved"\n ],\n "type": "string"\n },\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "instance_methods": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "properties": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attributes": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "is_atomic": {\n "default": false,\n "type": "boolean"\n },\n "is_copy": {\n "default": false,\n "type": "boolean"\n },\n "is_strong": {\n "default": false,\n "type": "boolean"\n },\n "is_weak": {\n "default": false,\n "type": "boolean"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "value",\n "is_weak",\n "is_atomic",\n "is_copy",\n "is_strong"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "getter": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_readonly": {\n "default": false,\n "type": "boolean"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "setter": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "type_encoding": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "type_encoding",\n "attributes",\n "is_readonly",\n "getter",\n "setter"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "protocols": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "name",\n "class_name",\n "class_source",\n "instance_methods",\n "class_methods",\n "protocols",\n "properties",\n "location",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "objc_classes": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "type": "string"\n },\n "artifact_sha256": {\n "type": "string"\n },\n "description": {\n "type": "string"\n },\n "kind": {\n "const": "binary_metadata",\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "instance_size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_meta_class": {\n "default": false,\n "type": "boolean"\n },\n "is_root_class": {\n "default": false,\n "type": "boolean"\n },\n "ivar_count": {\n "default": 0,\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "metaclass_address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "methods": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_optional": {\n "default": false,\n "type": "boolean"\n },\n "is_required": {\n "default": false,\n "type": "boolean"\n },\n "method_type": {\n "enum": [\n "instance",\n "class",\n "ivar_getter",\n "ivar_setter"\n ],\n "type": "string"\n },\n "selector": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "selector",\n "method_type",\n "address",\n "is_required",\n "is_optional"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "properties": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "attributes": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "is_atomic": {\n "default": false,\n "type": "boolean"\n },\n "is_copy": {\n "default": false,\n "type": "boolean"\n },\n "is_strong": {\n "default": false,\n "type": "boolean"\n },\n "is_weak": {\n "default": false,\n "type": "boolean"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "value",\n "is_weak",\n "is_atomic",\n "is_copy",\n "is_strong"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "getter": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_readonly": {\n "default": false,\n "type": "boolean"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "setter": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "type_encoding": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "type_encoding",\n "attributes",\n "is_readonly",\n "getter",\n "setter"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "protocols": {\n "default": [],\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "super_class": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "superclass_address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "super_class",\n "is_meta_class",\n "is_root_class",\n "methods",\n "properties",\n "protocols",\n "ivar_count",\n "instance_size"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "objc_dispatch_implementations": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "category": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "class_name": {\n "minLength": 1,\n "type": "string"\n },\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "implementation_address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "method_type": {\n "enum": [\n "instance",\n "class"\n ],\n "type": "string"\n },\n "selector": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "class_name",\n "selector",\n "method_type",\n "implementation_address",\n "location",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "objc_ivars": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "alignment": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "class_name": {\n "minLength": 1,\n "type": "string"\n },\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "type_encoding": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "class_name",\n "name",\n "type_encoding",\n "offset",\n "size",\n "alignment",\n "location",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "objc_protocol_records": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "adopted_protocols": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "methods": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_optional": {\n "default": false,\n "type": "boolean"\n },\n "is_required": {\n "default": false,\n "type": "boolean"\n },\n "method_type": {\n "enum": [\n "instance",\n "class",\n "ivar_getter",\n "ivar_setter"\n ],\n "type": "string"\n },\n "selector": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "selector",\n "method_type",\n "address",\n "is_required",\n "is_optional"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "optional_methods": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_optional": {\n "default": false,\n "type": "boolean"\n },\n "is_required": {\n "default": false,\n "type": "boolean"\n },\n "method_type": {\n "enum": [\n "instance",\n "class",\n "ivar_getter",\n "ivar_setter"\n ],\n "type": "string"\n },\n "selector": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "selector",\n "method_type",\n "address",\n "is_required",\n "is_optional"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "name",\n "adopted_protocols",\n "methods",\n "optional_methods",\n "location",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "objc_protocols": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "methods": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_optional": {\n "default": false,\n "type": "boolean"\n },\n "is_required": {\n "default": false,\n "type": "boolean"\n },\n "method_type": {\n "enum": [\n "instance",\n "class",\n "ivar_getter",\n "ivar_setter"\n ],\n "type": "string"\n },\n "selector": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "selector",\n "method_type",\n "address",\n "is_required",\n "is_optional"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "optional_methods": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_optional": {\n "default": false,\n "type": "boolean"\n },\n "is_required": {\n "default": false,\n "type": "boolean"\n },\n "method_type": {\n "enum": [\n "instance",\n "class",\n "ivar_getter",\n "ivar_setter"\n ],\n "type": "string"\n },\n "selector": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "selector",\n "method_type",\n "address",\n "is_required",\n "is_optional"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "properties": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "attributes": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "is_atomic": {\n "default": false,\n "type": "boolean"\n },\n "is_copy": {\n "default": false,\n "type": "boolean"\n },\n "is_strong": {\n "default": false,\n "type": "boolean"\n },\n "is_weak": {\n "default": false,\n "type": "boolean"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "value",\n "is_weak",\n "is_atomic",\n "is_copy",\n "is_strong"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "getter": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_readonly": {\n "default": false,\n "type": "boolean"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "setter": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "type_encoding": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "type_encoding",\n "attributes",\n "is_readonly",\n "getter",\n "setter"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "name",\n "methods",\n "optional_methods",\n "properties"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relative_pointers": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "displacement": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "field": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "indirect": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "indirectable": {\n "type": "boolean"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "field",\n "target",\n "displacement",\n "indirectable",\n "indirect",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "swift_conformances": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "module": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "protocol_name": {\n "minLength": 1,\n "type": "string"\n },\n "type_name": {\n "minLength": 1,\n "type": "string"\n },\n "witness_table": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "type_name",\n "protocol_name",\n "module",\n "witness_table",\n "location",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "swift_decls": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "access_level": {\n "default": "internal",\n "enum": [\n "private",\n "fileprivate",\n "internal",\n "public",\n "open"\n ],\n "type": "string"\n },\n "is_convenience_init": {\n "default": false,\n "type": "boolean"\n },\n "is_final": {\n "default": false,\n "type": "boolean"\n },\n "is_override": {\n "default": false,\n "type": "boolean"\n },\n "is_required": {\n "default": false,\n "type": "boolean"\n },\n "kind": {\n "enum": [\n "class",\n "struct",\n "enum",\n "protocol",\n "extension",\n "actor",\n "global_var",\n "global_func"\n ],\n "type": "string"\n },\n "module": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "protocols": {\n "default": [],\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "super_class": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "name",\n "module",\n "access_level",\n "super_class",\n "protocols",\n "is_final",\n "is_required",\n "is_convenience_init",\n "is_override"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "swift_dispatch_slots": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "implementation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "implementation_address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n "owner": {\n "minLength": 1,\n "type": "string"\n },\n "requirement": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "slot_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "table_kind": {\n "enum": [\n "witness_table",\n "class_vtable"\n ],\n "type": "string"\n },\n "thunk_address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "owner",\n "table_kind",\n "slot_index",\n "requirement",\n "implementation",\n "implementation_address",\n "thunk_address",\n "location",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "swift_symbols": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "decode": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "mangled_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "mangled_name",\n "address",\n "decode",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "objc_classes",\n "objc_protocols",\n "objc_categories",\n "swift_decls",\n "objc_ivars",\n "objc_protocol_records",\n "objc_dispatch_implementations",\n "swift_conformances",\n "swift_dispatch_slots",\n "swift_symbols",\n "relative_pointers",\n "coverage",\n "db_save_result"\n ],\n "type": "object"\n },\n "target_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "target_sha256",\n "provider",\n "analysis_profile_digest",\n "result"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Native Dispatch Metadata"\n },\n {\n "analysisOperation": "get_objc_classes",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Discover and deduplicate Objective-C class labels, optionally filtering by literal substring.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "pattern": ""\n }\n ],\n "properties": {\n "pattern": {\n "default": "",\n "description": "Literal text or regular expression used to filter matching results.",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "enhanced",\n "name": "get_objc_classes",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "classes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "count",\n "classes"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Get Objc Classes"\n },\n {\n "analysisOperation": "get_objc_protocols",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Discover and deduplicate Objective-C and Swift protocol labels.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "enhanced",\n "name": "get_objc_protocols",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "protocols": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "count",\n "protocols"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Get Objc Protocols"\n },\n {\n "analysisOperation": "batch_decompile",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Decompile each explicit procedure symbol or address concurrently. Returns ordered per-item success or error results and aggregate counts.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "addresses": []\n }\n ],\n "properties": {\n "addresses": {\n "default": [],\n "description": "Ordered provider-normalized procedure addresses to analyze.",\n "items": {\n "description": "A provider-normalized procedure address",\n "type": "string"\n },\n "type": "array"\n }\n },\n "type": "object"\n },\n "kind": "enhanced",\n "name": "batch_decompile",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n },\n "__schema1": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema1"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema1"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema1"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "failed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "items": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "pseudocode": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "ok",\n "type": "string"\n }\n },\n "required": [\n "address",\n "status",\n "pseudocode"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "error": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "invalid_request",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "unreadable_output",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "capability_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "provider_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "provider_timeout",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "cancelled",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "artifact_integrity_mismatch",\n "type": "string"\n },\n "details": {\n "additionalProperties": false,\n "properties": {\n "calculated_sha256": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "declared_sha256": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "logical_path": {\n "type": "string"\n },\n "unpacked": {\n "type": "boolean"\n }\n },\n "required": [\n "logical_path",\n "declared_sha256",\n "calculated_sha256",\n "unpacked"\n ],\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation",\n "details"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "artifact_operation_failed",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "evidence_integrity_mismatch",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "truncated",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "process_capture_failed",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "cleanup_incomplete",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "revision_conflict",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "configuration_invalid",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "target_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "execution_failure",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "status": {\n "const": "error",\n "type": "string"\n }\n },\n "required": [\n "address",\n "status",\n "error"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "succeeded": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "items",\n "total",\n "succeeded",\n "failed"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Batch Decompile"\n },\n {\n "analysisOperation": "get_call_graph",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Traverse the bound provider\'s caller or callee relationships from one symbol or address until the reachable graph is exhausted. Every node has an ok/error status and failures use safe typed projections; unresolved indirect calls may be missing and results are not a whole-program CFG.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "address": "0x1000",\n "direction": "forward"\n }\n ],\n "properties": {\n "address": {\n "description": "A provider-normalized procedure address",\n "type": "string"\n },\n "direction": {\n "default": "forward",\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "forward",\n "backward"\n ],\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "get_call_graph",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n },\n "__schema1": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema1"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema1"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema1"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "calls": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "ok",\n "type": "string"\n }\n },\n "required": [\n "address",\n "status",\n "calls"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "error": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "invalid_request",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "unreadable_output",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "capability_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "provider_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "provider_timeout",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "cancelled",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "artifact_integrity_mismatch",\n "type": "string"\n },\n "details": {\n "additionalProperties": false,\n "properties": {\n "calculated_sha256": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "declared_sha256": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "logical_path": {\n "type": "string"\n },\n "unpacked": {\n "type": "boolean"\n }\n },\n "required": [\n "logical_path",\n "declared_sha256",\n "calculated_sha256",\n "unpacked"\n ],\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation",\n "details"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "artifact_operation_failed",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "evidence_integrity_mismatch",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "truncated",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "process_capture_failed",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "cleanup_incomplete",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "revision_conflict",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "configuration_invalid",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "target_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "execution_failure",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "status": {\n "const": "error",\n "type": "string"\n }\n },\n "required": [\n "address",\n "status",\n "error"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Get Call Graph"\n },\n {\n "analysisOperation": "analyze_swift_types",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Categorize analyzed procedure names into Swift classes, structs, enums, protocols, extensions, and other symbols. Returns deduplicated names grouped with counts. Optionally select one category and/or apply a case-sensitive literal name filter; for example, use {category: \\"classes\\", pattern: \\"Account\\"} to find matching class symbols.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "category": "classes",\n "pattern": "Account"\n }\n ],\n "properties": {\n "category": {\n "description": "Limit results to one Swift symbol category.",\n "enum": [\n "classes",\n "structs",\n "enums",\n "protocols",\n "extensions",\n "other"\n ],\n "type": "string"\n },\n "pattern": {\n "description": "Case-sensitive literal filter applied to mangled symbol names.",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "enhanced",\n "name": "analyze_swift_types",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "categories": {\n "additionalProperties": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "count",\n "items"\n ],\n "type": "object"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "categories"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Analyze Swift Types"\n },\n {\n "analysisOperation": "find_xrefs_to_name",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Resolve an exact name against the bound provider\'s name inventory and return a resolved or unresolved result. Unresolved names use the stable name_not_found reason; this xref workflow returns address-only projections.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "name": "malloc"\n }\n ],\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n }\n },\n "required": [\n "name"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "find_xrefs_to_name",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "status": {\n "const": "resolved",\n "type": "string"\n },\n "xrefs": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "name",\n "address",\n "xrefs"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "reason": {\n "const": "name_not_found",\n "type": "string"\n },\n "status": {\n "const": "unresolved",\n "type": "string"\n }\n },\n "required": [\n "status",\n "name",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Find Xrefs To Name"\n },\n {\n "analysisOperation": "binary_overview",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return native-binary metadata, every segment with its length, and exhaustive procedure/string counts. Use search_procedures, list_procedures, or analyze_function directly when you already know which procedure to inspect.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "enhanced",\n "name": "binary_overview",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "document": {\n "type": "string"\n },\n "procedure_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "segment_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "segments": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "type": "string"\n },\n "length": {\n "minimum": 0,\n "type": "number"\n },\n "name": {\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "start",\n "end",\n "length"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "string_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "document",\n "segments",\n "segment_count",\n "procedure_count",\n "string_count"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Binary Overview"\n },\n {\n "analysisOperation": "analyze_function",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Build a dossier for one native function identified by symbol or provider-returned address. Returns identity with complete inclusive body ranges or explicit unknown, provider-specific pseudocode and assembly, comments, calls, references, referenced strings/names, local CFG blocks, and available native API boundary observations.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "procedure": {\n "description": "A procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "analyze_function",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "assembly": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "basic_blocks": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "type": "string"\n },\n "start": {\n "type": "string"\n },\n "successors": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "start",\n "end",\n "successors"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "callees": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "callers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "comments": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "comment",\n "inline"\n ],\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "kind",\n "text"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "incoming_references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "call": {\n "type": "boolean"\n },\n "computed": {\n "type": "boolean"\n },\n "conditional": {\n "type": "boolean"\n },\n "data": {\n "type": "boolean"\n },\n "external": {\n "type": "boolean"\n },\n "flow": {\n "type": "boolean"\n },\n "indirect": {\n "type": "boolean"\n },\n "jump": {\n "type": "boolean"\n },\n "operand_index": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "primary": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "read": {\n "type": "boolean"\n },\n "terminal": {\n "type": "boolean"\n },\n "type": {\n "type": "string"\n },\n "write": {\n "type": "boolean"\n }\n },\n "required": [\n "available",\n "provenance",\n "type",\n "flow",\n "call",\n "jump",\n "data",\n "read",\n "write",\n "indirect",\n "computed",\n "conditional",\n "terminal",\n "primary",\n "operand_index",\n "external"\n ],\n "type": "object"\n }\n ]\n },\n "source_address": {\n "type": "string"\n },\n "source_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_address": {\n "type": "string"\n },\n "target_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "source_address",\n "target_address",\n "source_procedure",\n "target_procedure",\n "kind"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "default": [],\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "native_api": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "residual_unknowns": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "available",\n "reason",\n "residual_unknowns"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "calling_convention": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-and-stack-variables",\n "compiler-generated-control-flow",\n "pointer-arithmetic",\n "pseudocode"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "jump_tables": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "data_sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "entry_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "entry_size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "provenance": {\n "enum": [\n "ghidra-decompiler-load-table",\n "ghidra-dispatch-block-data-reference",\n "ghidra-aarch64-byte-table",\n "ghidra-aarch64-halfword-table"\n ],\n "type": "string"\n }\n },\n "required": [\n "address",\n "provenance",\n "entry_size_bytes",\n "entry_count",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "default_targets": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "target_address": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_address",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "dispatch_address": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "mappings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "case_value": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "target_address": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "case_value",\n "target_address",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "dispatch_address",\n "data_sources",\n "mappings",\n "default_targets",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "parameters": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "data_type": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-or-stack-storage",\n "compiler-generated-variable"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "role": {\n "const": "parameter",\n "type": "string"\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "storage": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type",\n "size_bytes",\n "storage",\n "confidence",\n "evidence",\n "decompiler_artifacts",\n "role"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "pseudocode": {\n "additionalProperties": false,\n "properties": {\n "classification": {\n "const": "decompiler-generated-non-source",\n "type": "string"\n },\n "compilable": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "classification",\n "compilable"\n ],\n "type": "object"\n },\n "return_type": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "data_type": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-or-stack-storage",\n "compiler-generated-variable"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "type": "null"\n },\n "ordinal": {\n "type": "null"\n },\n "role": {\n "const": "return",\n "type": "string"\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "storage": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type",\n "size_bytes",\n "storage",\n "confidence",\n "evidence",\n "decompiler_artifacts",\n "role"\n ],\n "type": "object"\n },\n "signature_source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "available",\n "provenance",\n "signature_source",\n "calling_convention",\n "return_type",\n "parameters",\n "jump_tables",\n "pseudocode",\n "decompiler_artifacts",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "native_value_flow": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "def_use": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "definition": {\n "minLength": 1,\n "type": "string"\n },\n "input_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "use": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "definition",\n "use",\n "input_index"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "effects": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "memory_read",\n "memory_write",\n "conditional_branch",\n "indirect_branch",\n "direct_call",\n "indirect_call"\n ],\n "type": "string"\n },\n "operand_input": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "value_input": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "operation",\n "kind",\n "operand_input",\n "value_input"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "known_omitted_edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "known_omitted_inputs": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "omitted_operations_lower_bound": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "operations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "block_membership": {\n "default": "unavailable",\n "enum": [\n "member",\n "detached",\n "unavailable"\n ],\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "inputs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "constant_hex": {\n "anyOf": [\n {\n "pattern": "^[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "constant",\n "address",\n "register",\n "unique",\n "other"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "size_bytes",\n "location",\n "constant_hex"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "is_dead": {\n "type": "boolean"\n },\n "opcode": {\n "minLength": 1,\n "type": "string"\n },\n "output": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "constant_hex": {\n "anyOf": [\n {\n "pattern": "^[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "constant",\n "address",\n "register",\n "unique",\n "other"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "size_bytes",\n "location",\n "constant_hex"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "id",\n "address",\n "sequence",\n "opcode",\n "is_dead",\n "block_membership",\n "inputs",\n "output"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "parameter_uses": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "input_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "use": {\n "type": "string"\n }\n },\n "required": [\n "ordinal",\n "use",\n "input_index"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "parameters": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "data_type": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provenance": {\n "const": "ghidra-high-pcode",\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "available",\n "provenance",\n "operations",\n "def_use",\n "effects",\n "parameters",\n "parameter_uses",\n "truncated",\n "omitted_operations_lower_bound",\n "known_omitted_inputs",\n "known_omitted_edges",\n "limitations"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "outgoing_references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "call": {\n "type": "boolean"\n },\n "computed": {\n "type": "boolean"\n },\n "conditional": {\n "type": "boolean"\n },\n "data": {\n "type": "boolean"\n },\n "external": {\n "type": "boolean"\n },\n "flow": {\n "type": "boolean"\n },\n "indirect": {\n "type": "boolean"\n },\n "jump": {\n "type": "boolean"\n },\n "operand_index": {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n "primary": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "read": {\n "type": "boolean"\n },\n "terminal": {\n "type": "boolean"\n },\n "type": {\n "type": "string"\n },\n "write": {\n "type": "boolean"\n }\n },\n "required": [\n "available",\n "provenance",\n "type",\n "flow",\n "call",\n "jump",\n "data",\n "read",\n "write",\n "indirect",\n "computed",\n "conditional",\n "terminal",\n "primary",\n "operand_index",\n "external"\n ],\n "type": "object"\n }\n ]\n },\n "source_address": {\n "type": "string"\n },\n "source_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_address": {\n "type": "string"\n },\n "target_procedure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "source_address",\n "target_address",\n "source_procedure",\n "target_procedure",\n "kind"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "locals": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "description": {\n "type": "string"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "description",\n "provenance"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "signature": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body",\n "signature",\n "locals"\n ],\n "type": "object"\n },\n "pseudocode": {\n "type": "string"\n },\n "referenced_names": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "source_address": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value",\n "source_address"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "referenced_strings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "source_address": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "value",\n "source_address"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "procedure",\n "pseudocode",\n "assembly",\n "comments",\n "callers",\n "callees",\n "incoming_references",\n "outgoing_references",\n "referenced_strings",\n "referenced_names",\n "basic_blocks",\n "native_api",\n "native_value_flow",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Analyze Function"\n },\n {\n "analysisOperation": "inspect_native_api",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Analyze a native API boundary in one function identified by symbol or provider-returned address. Returns confidence, evidence, jump-table dispatch/data/case mappings, separately evidenced default targets, unsupported branches, and residual unknowns. Null case values mean unresolved cases, never a known default.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "procedure": "main"\n }\n ],\n "properties": {\n "procedure": {\n "description": "A procedure name or address",\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "inspect_native_api",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "boundary": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "residual_unknowns": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "available",\n "reason",\n "residual_unknowns"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "calling_convention": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-and-stack-variables",\n "compiler-generated-control-flow",\n "pointer-arithmetic",\n "pseudocode"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "jump_tables": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "data_sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "entry_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "entry_size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "provenance": {\n "enum": [\n "ghidra-decompiler-load-table",\n "ghidra-dispatch-block-data-reference",\n "ghidra-aarch64-byte-table",\n "ghidra-aarch64-halfword-table"\n ],\n "type": "string"\n }\n },\n "required": [\n "address",\n "provenance",\n "entry_size_bytes",\n "entry_count",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "default_targets": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "target_address": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_address",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "dispatch_address": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "mappings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "case_value": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "target_address": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "case_value",\n "target_address",\n "confidence",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "dispatch_address",\n "data_sources",\n "mappings",\n "default_targets",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "parameters": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "data_type": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-or-stack-storage",\n "compiler-generated-variable"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "role": {\n "const": "parameter",\n "type": "string"\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "storage": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type",\n "size_bytes",\n "storage",\n "confidence",\n "evidence",\n "decompiler_artifacts",\n "role"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "pseudocode": {\n "additionalProperties": false,\n "properties": {\n "classification": {\n "const": "decompiler-generated-non-source",\n "type": "string"\n },\n "compilable": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "classification",\n "compilable"\n ],\n "type": "object"\n },\n "return_type": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "data_type": {\n "minLength": 1,\n "type": "string"\n },\n "decompiler_artifacts": {\n "items": {\n "enum": [\n "recovered-signature",\n "register-or-stack-storage",\n "compiler-generated-variable"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "signature-source",\n "decompiler-type",\n "type-lock",\n "calling-convention",\n "jump-table"\n ],\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "source",\n "detail"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "type": "null"\n },\n "ordinal": {\n "type": "null"\n },\n "role": {\n "const": "return",\n "type": "string"\n },\n "size_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "storage": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type",\n "size_bytes",\n "storage",\n "confidence",\n "evidence",\n "decompiler_artifacts",\n "role"\n ],\n "type": "object"\n },\n "signature_source": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "available",\n "provenance",\n "signature_source",\n "calling_convention",\n "return_type",\n "parameters",\n "jump_tables",\n "pseudocode",\n "decompiler_artifacts",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "address",\n "name"\n ],\n "type": "object"\n },\n "residual_unknowns": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "substeps": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "observations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "enum": [\n "analyze_function",\n "project_native_api_boundary",\n "preserve_residual_unknowns"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "completed",\n "unsupported"\n ],\n "type": "string"\n }\n },\n "required": [\n "operation",\n "status",\n "observations"\n ],\n "type": "object"\n },\n "maxItems": 3,\n "minItems": 3,\n "type": "array"\n },\n "unsupported_branches": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "procedure",\n "boundary",\n "substeps",\n "unsupported_branches",\n "residual_unknowns"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Native Api"\n },\n {\n "analysisOperation": "trace_feature",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Trace a literal feature query through every matching string and procedure, their xrefs, and truthful containing-procedure resolution. Returns observations, operation count, and residual unknowns without inferring reference kinds.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "case_sensitive": false,\n "query": "license"\n }\n ],\n "properties": {\n "case_sensitive": {\n "default": false,\n "description": "Whether text matching distinguishes uppercase and lowercase.",\n "type": "boolean"\n },\n "query": {\n "description": "Non-empty feature or text query to investigate.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "query"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "trace_feature",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "matches": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "type": {\n "enum": [\n "string",\n "procedure"\n ],\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "type",\n "address",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "query": {\n "type": "string"\n },\n "references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "containing_procedure": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "found": {\n "const": true,\n "type": "boolean"\n },\n "procedure": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "body": {\n "default": {\n "available": false,\n "reason": "The provider did not report complete function body ranges."\n },\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "contains_entry": {\n "type": "boolean"\n },\n "non_contiguous": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "description": "Inclusive last address of this observed body range.",\n "type": "string"\n },\n "start": {\n "type": "string"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "span_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "total_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "available",\n "provenance",\n "ranges",\n "total_bytes",\n "span_bytes",\n "non_contiguous",\n "contains_entry"\n ],\n "type": "object"\n }\n ]\n },\n "classification": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "external": {\n "type": "boolean"\n },\n "provenance": {\n "minLength": 1,\n "type": "string"\n },\n "thunk": {\n "type": "boolean"\n },\n "thunk_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "external",\n "thunk",\n "thunk_target",\n "provenance"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name",\n "classification",\n "body"\n ],\n "type": "object"\n },\n "query_address": {\n "type": "string"\n }\n },\n "required": [\n "query_address",\n "found",\n "procedure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "found": {\n "const": false,\n "type": "boolean"\n },\n "procedure": {\n "type": "null"\n },\n "query_address": {\n "type": "string"\n },\n "reason": {\n "enum": [\n "outside_segments",\n "not_in_procedure"\n ],\n "type": "string"\n }\n },\n "required": [\n "query_address",\n "found",\n "procedure",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "source_address": {\n "type": "string"\n },\n "target_address": {\n "type": "string"\n }\n },\n "required": [\n "target_address",\n "source_address",\n "containing_procedure"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "residual_unknowns": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "search_mode": {\n "const": "literal",\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "query",\n "search_mode",\n "matches",\n "references",\n "truncated",\n "residual_unknowns"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Feature"\n },\n {\n "analysisOperation": "trace_call_path",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Trace direct callers or callees from one exact procedure address until the graph is exhausted or the optional goal is reached. Returns visited nodes, direct-call edges, a shortest traversal path, provider failures, and residual unknowns; unresolved indirect calls remain unknown.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "direction": "forward",\n "goal": "0x1100",\n "start": "0x1000"\n }\n ],\n "properties": {\n "direction": {\n "default": "forward",\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "forward",\n "backward"\n ],\n "type": "string"\n },\n "goal": {\n "description": "An optional provider-normalized destination address",\n "type": "string"\n },\n "start": {\n "description": "A provider-normalized procedure address",\n "type": "string"\n }\n },\n "required": [\n "start"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "trace_call_path",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n },\n "__schema1": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema1"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema1"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema1"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "direction": {\n "enum": [\n "forward",\n "backward"\n ],\n "type": "string"\n },\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "discovery_depth": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source_address": {\n "type": "string"\n },\n "target_address": {\n "type": "string"\n }\n },\n "required": [\n "source_address",\n "target_address",\n "discovery_depth"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "failures": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "error": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "invalid_request",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "unreadable_output",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "capability_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "provider_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "provider_timeout",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "cancelled",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "artifact_integrity_mismatch",\n "type": "string"\n },\n "details": {\n "additionalProperties": false,\n "properties": {\n "calculated_sha256": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "declared_sha256": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "logical_path": {\n "type": "string"\n },\n "unpacked": {\n "type": "boolean"\n }\n },\n "required": [\n "logical_path",\n "declared_sha256",\n "calculated_sha256",\n "unpacked"\n ],\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation",\n "details"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "artifact_operation_failed",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "evidence_integrity_mismatch",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "truncated",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "process_capture_failed",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "cleanup_incomplete",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "revision_conflict",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "configuration_invalid",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "target_unavailable",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "category": {\n "enum": [\n "invalid_input",\n "unsupported_provider",\n "integrity_mismatch",\n "truncated",\n "cancelled",\n "timeout",\n "unavailable",\n "execution_failure"\n ],\n "type": "string"\n },\n "code": {\n "const": "execution_failure",\n "type": "string"\n },\n "details": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "remediation": {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "retryable": {\n "type": "boolean"\n }\n },\n "required": [\n "code",\n "category",\n "message",\n "retryable",\n "remediation"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "address",\n "error"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "goal": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "goal_status": {\n "enum": [\n "not_requested",\n "reached",\n "not_reached"\n ],\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "depth": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "address",\n "depth"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "residual_unknowns": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "start": {\n "type": "string"\n },\n "traversal": {\n "additionalProperties": false,\n "properties": {\n "nodes_visited": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "nodes_visited"\n ],\n "type": "object"\n },\n "traversal_path": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "start",\n "goal",\n "direction",\n "goal_status",\n "nodes",\n "edges",\n "traversal_path",\n "failures",\n "traversal",\n "truncated",\n "residual_unknowns",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Call Path"\n },\n {\n "analysisOperation": "trace_native_ui_action",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Trace one unique compiled UI action, object ID, native symbol or exact function address through authored connections, encoded or symbolized Objective-C handlers, and bounded static call references. Typed direct and resolved indirect calls, ambiguous candidates, inferred untyped provider callees, and targetless sites remain distinct. Runtime reachability is unknown; use trace_native_values for recovered value dependencies.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "action": "buildTapped:",\n "max_depth": 8,\n "max_edges": 500,\n "max_nodes": 250\n }\n ],\n "properties": {\n "action": {\n "description": "A unique compiled UI action selector, interface object ID, native symbol or exact function address",\n "minLength": 1,\n "type": "string"\n },\n "max_depth": {\n "default": 8,\n "description": "Maximum permitted depth for this operation.",\n "maximum": 32,\n "minimum": 0,\n "type": "integer"\n },\n "max_edges": {\n "default": 500,\n "description": "Maximum permitted edges for this operation.",\n "maximum": 5000,\n "minimum": 1,\n "type": "integer"\n },\n "max_nodes": {\n "default": 250,\n "description": "Maximum permitted nodes for this operation.",\n "maximum": 2000,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "action"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "trace_native_ui_action",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "examined": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "facet": {\n "minLength": 1,\n "type": "string"\n },\n "omitted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "unsupported",\n "not_requested"\n ],\n "type": "string"\n }\n },\n "required": [\n "facet",\n "status",\n "reason",\n "examined",\n "omitted"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "direction": {\n "enum": [\n "forward",\n "backward"\n ],\n "type": "string"\n },\n "edges": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "from": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "relation": {\n "enum": [\n "contains",\n "outlet_to",\n "target_action",\n "segue_to",\n "objc_dispatch",\n "swift_witness_dispatch",\n "swift_vtable_dispatch",\n "thunk_to",\n "direct_call",\n "indirect_call",\n "provider_call",\n "reads",\n "writes",\n "flows_to",\n "controls"\n ],\n "type": "string"\n },\n "resolution": {\n "enum": [\n "observed",\n "inferred",\n "resolved",\n "ambiguous"\n ],\n "type": "string"\n },\n "to": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "from",\n "to",\n "relation",\n "resolution",\n "evidence",\n "limitations"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "from": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "relation": {\n "enum": [\n "contains",\n "outlet_to",\n "target_action",\n "segue_to",\n "objc_dispatch",\n "swift_witness_dispatch",\n "swift_vtable_dispatch",\n "thunk_to",\n "direct_call",\n "indirect_call",\n "provider_call",\n "reads",\n "writes",\n "flows_to",\n "controls"\n ],\n "type": "string"\n },\n "resolution": {\n "const": "unresolved",\n "type": "string"\n },\n "to": {\n "type": "null"\n }\n },\n "required": [\n "id",\n "from",\n "to",\n "relation",\n "resolution",\n "reason",\n "evidence",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attributes": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "storyboard",\n "scene",\n "view_controller",\n "view",\n "control",\n "constraint",\n "layout_guide",\n "resource",\n "placeholder",\n "external_object",\n "outlet",\n "action",\n "segue",\n "objc_class",\n "objc_selector",\n "swift_declaration",\n "swift_requirement",\n "dispatch_table",\n "dispatch_slot",\n "thunk",\n "function",\n "state_value",\n "global_value",\n "field",\n "branch_condition",\n "sink",\n "unknown"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "kind",\n "name",\n "location",\n "attributes",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "version",\n "tool_version"\n ],\n "type": "object"\n },\n "reached_depth": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "start": {\n "minLength": 1,\n "type": "string"\n },\n "target_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n },\n "unresolved": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "from": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "relation": {\n "enum": [\n "contains",\n "outlet_to",\n "target_action",\n "segue_to",\n "objc_dispatch",\n "swift_witness_dispatch",\n "swift_vtable_dispatch",\n "thunk_to",\n "direct_call",\n "indirect_call",\n "provider_call",\n "reads",\n "writes",\n "flows_to",\n "controls"\n ],\n "type": "string"\n },\n "resolution": {\n "enum": [\n "observed",\n "inferred",\n "resolved",\n "ambiguous"\n ],\n "type": "string"\n },\n "to": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "from",\n "to",\n "relation",\n "resolution",\n "evidence",\n "limitations"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "from": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "relation": {\n "enum": [\n "contains",\n "outlet_to",\n "target_action",\n "segue_to",\n "objc_dispatch",\n "swift_witness_dispatch",\n "swift_vtable_dispatch",\n "thunk_to",\n "direct_call",\n "indirect_call",\n "provider_call",\n "reads",\n "writes",\n "flows_to",\n "controls"\n ],\n "type": "string"\n },\n "resolution": {\n "const": "unresolved",\n "type": "string"\n },\n "to": {\n "type": "null"\n }\n },\n "required": [\n "id",\n "from",\n "to",\n "relation",\n "resolution",\n "reason",\n "evidence",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "target_sha256",\n "provider",\n "start",\n "direction",\n "nodes",\n "edges",\n "unresolved",\n "reached_depth",\n "truncated",\n "reason",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Native Ui Action"\n },\n {\n "analysisOperation": "trace_native_values",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Trace a bounded static dependency graph from one explicit native procedure. Includes high-p-code def-use, constants, operators, memory and branch operations, resolved call edges, and derived argument/parameter and return/output bindings. Missing bindings, alias semantics, persistent state and RNG roles remain unknown. Budgets bound depth, decompilations, graph size and payloads; nodes are paginated.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "limit": 1000,\n "max_call_sites": 256,\n "max_depth": 3,\n "max_edges": 10000,\n "max_functions": 16,\n "max_nodes": 5000,\n "offset": 0,\n "procedure": "0x1000"\n }\n ],\n "properties": {\n "limit": {\n "default": 1000,\n "description": "Value for limit.",\n "maximum": 5000,\n "minimum": 1,\n "type": "integer"\n },\n "max_call_sites": {\n "default": 256,\n "description": "Maximum permitted call sites for this operation.",\n "maximum": 4096,\n "minimum": 1,\n "type": "integer"\n },\n "max_depth": {\n "default": 3,\n "description": "Maximum permitted depth for this operation.",\n "maximum": 16,\n "minimum": 0,\n "type": "integer"\n },\n "max_edges": {\n "default": 10000,\n "description": "Maximum permitted edges for this operation.",\n "maximum": 40000,\n "minimum": 1,\n "type": "integer"\n },\n "max_functions": {\n "default": 16,\n "description": "Maximum permitted functions for this operation.",\n "maximum": 64,\n "minimum": 1,\n "type": "integer"\n },\n "max_nodes": {\n "default": 5000,\n "description": "Maximum permitted nodes for this operation.",\n "maximum": 20000,\n "minimum": 1,\n "type": "integer"\n },\n "offset": {\n "default": 0,\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "procedure": {\n "description": "Value for procedure.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "procedure"\n ],\n "type": "object"\n },\n "kind": "enhanced",\n "name": "trace_native_values",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "decompilations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "type": "string"\n },\n "input_index": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "def-use",\n "call",\n "parameter-use",\n "argument-binding",\n "return-binding"\n ],\n "type": "string"\n },\n "source": {\n "type": "string"\n },\n "status": {\n "enum": [\n "derived",\n "direct",\n "resolved-indirect",\n "candidate"\n ],\n "type": "string"\n },\n "target": {\n "type": "string"\n }\n },\n "required": [\n "source",\n "target",\n "input_index",\n "kind",\n "status",\n "evidence_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "next_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "type": "string"\n },\n "id": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "operation",\n "parameter"\n ],\n "type": "string"\n },\n "operation": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "block_membership": {\n "default": "unavailable",\n "enum": [\n "member",\n "detached",\n "unavailable"\n ],\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "inputs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "constant_hex": {\n "anyOf": [\n {\n "pattern": "^[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "constant",\n "address",\n "register",\n "unique",\n "other"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "size_bytes",\n "location",\n "constant_hex"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "is_dead": {\n "type": "boolean"\n },\n "opcode": {\n "minLength": 1,\n "type": "string"\n },\n "output": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "constant_hex": {\n "anyOf": [\n {\n "pattern": "^[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "constant",\n "address",\n "register",\n "unique",\n "other"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "size_bytes",\n "location",\n "constant_hex"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "id",\n "address",\n "sequence",\n "opcode",\n "is_dead",\n "block_membership",\n "inputs",\n "output"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "parameter": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "data_type": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "ordinal": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "ordinal",\n "name",\n "data_type"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "procedure": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "procedure",\n "evidence_id",\n "kind",\n "operation",\n "parameter"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "procedures": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "analysis_profile_digest": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "depth": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "evidence_id": {\n "type": "string"\n },\n "provider": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "depth",\n "evidence_id",\n "provider",\n "analysis_profile_digest"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "seed": {\n "type": "string"\n },\n "target_sha256": {\n "type": "string"\n },\n "total_edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "type": "boolean"\n },\n "unknowns": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "procedure": {\n "type": "string"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "procedure",\n "address",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "seed",\n "target_sha256",\n "nodes",\n "edges",\n "procedures",\n "unknowns",\n "total_nodes",\n "total_edges",\n "decompilations",\n "next_offset",\n "truncated",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Native Values"\n },\n {\n "analysisOperation": "observe_native_ui",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Observe an already-running native app by PID and window ID, bound to the active Mach-O target. Captures a bounded accessibility tree and selected-window screenshot; OS permission denial fails without broad capture or target launch.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "pid": 123,\n "window_id": 456\n }\n ],\n "properties": {\n "accessibility": {\n "default": true,\n "description": "Value for accessibility.",\n "type": "boolean"\n },\n "max_nodes": {\n "default": 500,\n "description": "Maximum accessibility nodes per capture; increase for large windows.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "pid": {\n "description": "Value for pid.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "screenshot": {\n "default": true,\n "description": "Value for screenshot.",\n "type": "boolean"\n },\n "window_id": {\n "description": "Value for window id.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id"\n ],\n "type": "object"\n },\n "kind": "native-provider",\n "name": "observe_native_ui",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "initial": {\n "additionalProperties": false,\n "properties": {\n "gaps": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "actions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "children_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "title": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "role",\n "title",\n "value",\n "actions",\n "children_count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "screenshot": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "base64": {\n "minLength": 4,\n "type": "string"\n },\n "height": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "mime_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "width": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "mime_type",\n "base64",\n "sha256",\n "width",\n "height"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "truncated": {\n "type": "boolean"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "executable": {\n "type": "string"\n },\n "launch_time": {\n "type": "number"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "title": {\n "type": "string"\n },\n "window_id": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id",\n "executable",\n "launch_time",\n "title"\n ],\n "type": "object"\n }\n },\n "required": [\n "window",\n "nodes",\n "truncated",\n "screenshot",\n "gaps"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "restore": {\n "const": "leave-as-is",\n "type": "string"\n },\n "steps": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "after": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "gaps": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "actions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "children_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "title": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "role",\n "title",\n "value",\n "actions",\n "children_count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "screenshot": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "base64": {\n "minLength": 4,\n "type": "string"\n },\n "height": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "mime_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "width": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "mime_type",\n "base64",\n "sha256",\n "width",\n "height"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "truncated": {\n "type": "boolean"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "executable": {\n "type": "string"\n },\n "launch_time": {\n "type": "number"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "title": {\n "type": "string"\n },\n "window_id": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id",\n "executable",\n "launch_time",\n "title"\n ],\n "type": "object"\n }\n },\n "required": [\n "window",\n "nodes",\n "truncated",\n "screenshot",\n "gaps"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "before": {\n "additionalProperties": false,\n "properties": {\n "gaps": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "actions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "children_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "title": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "role",\n "title",\n "value",\n "actions",\n "children_count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "screenshot": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "base64": {\n "minLength": 4,\n "type": "string"\n },\n "height": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "mime_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "width": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "mime_type",\n "base64",\n "sha256",\n "width",\n "height"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "truncated": {\n "type": "boolean"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "executable": {\n "type": "string"\n },\n "launch_time": {\n "type": "number"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "title": {\n "type": "string"\n },\n "window_id": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id",\n "executable",\n "launch_time",\n "title"\n ],\n "type": "object"\n }\n },\n "required": [\n "window",\n "nodes",\n "truncated",\n "screenshot",\n "gaps"\n ],\n "type": "object"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "type": "string"\n },\n "outcome": {\n "enum": [\n "completed",\n "failed",\n "cancelled"\n ],\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "index",\n "kind",\n "before",\n "after",\n "outcome",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "target_sha256": {\n "type": "string"\n }\n },\n "required": [\n "target_sha256",\n "initial",\n "steps",\n "restore",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Observe Native Ui"\n },\n {\n "analysisOperation": "capture_native_ui_scenario",\n "annotations": {\n "destructiveHint": true,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Run selected-element press, increment/decrement scroll, AXValue text entry and bounded wait steps in one exact native app window. Returns ordered before/after captures and action/capture failures. Actions can change app data, network activity and persistent state; application state is left as-is and restoration is not attempted.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": true,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": true,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "pid": 123,\n "steps": [\n {\n "kind": "wait",\n "milliseconds": 100\n }\n ],\n "window_id": 456\n }\n ],\n "properties": {\n "accessibility": {\n "default": true,\n "description": "Value for accessibility.",\n "type": "boolean"\n },\n "max_nodes": {\n "default": 500,\n "description": "Maximum accessibility nodes per capture; increase for large windows.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "pid": {\n "description": "Value for pid.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "screenshot": {\n "default": true,\n "description": "Value for screenshot.",\n "type": "boolean"\n },\n "steps": {\n "description": "Value for steps.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "click",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "maxItems": 32,\n "type": "array"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "direction": {\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "increment",\n "decrement"\n ],\n "type": "string"\n },\n "kind": {\n "const": "scroll",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "maxItems": 32,\n "type": "array"\n }\n },\n "required": [\n "kind",\n "path",\n "direction"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "key-entry",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "maxItems": 32,\n "type": "array"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path",\n "text"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "wait",\n "description": "Value for kind.",\n "type": "string"\n },\n "milliseconds": {\n "description": "Wait duration in milliseconds, within the 180-second operation deadline.",\n "maximum": 180000,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "milliseconds"\n ],\n "type": "object"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n },\n "window_id": {\n "description": "Value for window id.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id",\n "steps"\n ],\n "type": "object"\n },\n "kind": "native-provider",\n "name": "capture_native_ui_scenario",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "initial": {\n "additionalProperties": false,\n "properties": {\n "gaps": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "actions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "children_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "title": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "role",\n "title",\n "value",\n "actions",\n "children_count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "screenshot": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "base64": {\n "minLength": 4,\n "type": "string"\n },\n "height": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "mime_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "width": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "mime_type",\n "base64",\n "sha256",\n "width",\n "height"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "truncated": {\n "type": "boolean"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "executable": {\n "type": "string"\n },\n "launch_time": {\n "type": "number"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "title": {\n "type": "string"\n },\n "window_id": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id",\n "executable",\n "launch_time",\n "title"\n ],\n "type": "object"\n }\n },\n "required": [\n "window",\n "nodes",\n "truncated",\n "screenshot",\n "gaps"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "restore": {\n "const": "leave-as-is",\n "type": "string"\n },\n "steps": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "after": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "gaps": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "actions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "children_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "title": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "role",\n "title",\n "value",\n "actions",\n "children_count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "screenshot": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "base64": {\n "minLength": 4,\n "type": "string"\n },\n "height": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "mime_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "width": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "mime_type",\n "base64",\n "sha256",\n "width",\n "height"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "truncated": {\n "type": "boolean"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "executable": {\n "type": "string"\n },\n "launch_time": {\n "type": "number"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "title": {\n "type": "string"\n },\n "window_id": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id",\n "executable",\n "launch_time",\n "title"\n ],\n "type": "object"\n }\n },\n "required": [\n "window",\n "nodes",\n "truncated",\n "screenshot",\n "gaps"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "before": {\n "additionalProperties": false,\n "properties": {\n "gaps": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "actions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "children_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "title": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "role",\n "title",\n "value",\n "actions",\n "children_count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "screenshot": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "base64": {\n "minLength": 4,\n "type": "string"\n },\n "height": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "mime_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "width": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "mime_type",\n "base64",\n "sha256",\n "width",\n "height"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "truncated": {\n "type": "boolean"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "executable": {\n "type": "string"\n },\n "launch_time": {\n "type": "number"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "title": {\n "type": "string"\n },\n "window_id": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "window_id",\n "executable",\n "launch_time",\n "title"\n ],\n "type": "object"\n }\n },\n "required": [\n "window",\n "nodes",\n "truncated",\n "screenshot",\n "gaps"\n ],\n "type": "object"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "type": "string"\n },\n "outcome": {\n "enum": [\n "completed",\n "failed",\n "cancelled"\n ],\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "index",\n "kind",\n "before",\n "after",\n "outcome",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "target_sha256": {\n "type": "string"\n }\n },\n "required": [\n "target_sha256",\n "initial",\n "steps",\n "restore",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Capture Native Ui Scenario"\n },\n {\n "analysisOperation": "observe_native_calls",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Launch the active Mach-O as an owned process under LLDB and record each entry into caller-selected functions or Objective-C methods: thread, module, symbol, load and file address, raw argument registers, selector, receiver class and optional caller frames. Every hit auto-continues. No expression is evaluated and no other process is attached. The process is killed when max_events or duration_ms is reached; exit status, captured stdout/stderr, unresolved breakpoints and limitations are returned inline. The process runs with the current user\'s permissions and may change files, show UI or use the network. A hardened-runtime target needs the get-task-allow entitlement.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": true,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "arguments": [\n "--help"\n ],\n "backtrace_frames": 4,\n "breakpoints": [\n {\n "class_name": "NSURLSession",\n "kind": "objc-method",\n "selector": "dataTaskWithRequest:completionHandler:"\n },\n {\n "kind": "function",\n "module": "libsystem_kernel.dylib",\n "name": "open"\n }\n ],\n "duration_ms": 5000\n }\n ],\n "properties": {\n "argument_registers": {\n "default": 4,\n "description": "Integer argument registers to record per entry (arm64 has 8, x86_64 has 6).",\n "maximum": 8,\n "minimum": 0,\n "type": "integer"\n },\n "arguments": {\n "default": [],\n "description": "Value for arguments.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "backtrace_frames": {\n "default": 0,\n "description": "Caller frames to record per entry.",\n "maximum": 64,\n "minimum": 0,\n "type": "integer"\n },\n "breakpoints": {\n "description": "Value for breakpoints.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "function",\n "description": "Value for kind.",\n "type": "string"\n },\n "module": {\n "description": "Image file name that defines the symbol, such as `libsystem_kernel.dylib`; omit to match every image.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact symbol name, such as `open`, a C++ or mangled Swift symbol, or `-[NSString length]`.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "class_name": {\n "description": "Implementing class; omit to match every class.",\n "minLength": 1,\n "pattern": "^[^\\\\s[\\\\]]+$",\n "type": "string"\n },\n "kind": {\n "const": "objc-method",\n "description": "Value for kind.",\n "type": "string"\n },\n "method_type": {\n "default": "any",\n "description": "Value for method type.",\n "enum": [\n "instance",\n "class",\n "any"\n ],\n "type": "string"\n },\n "selector": {\n "description": "Selector such as `dataTaskWithRequest:completionHandler:`.",\n "minLength": 1,\n "pattern": "^[^\\\\s[\\\\]]+$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector"\n ],\n "type": "object"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n },\n "duration_ms": {\n "default": 10000,\n "description": "Observation window; the process is killed if it is still running.",\n "maximum": 600000,\n "minimum": 100,\n "type": "integer"\n },\n "environment": {\n "additionalProperties": {\n "pattern": "^[^\\\\0]*$",\n "type": "string"\n },\n "default": {},\n "description": "Overrides on top of the environment REA runs with.",\n "propertyNames": {\n "minLength": 1,\n "pattern": "^[^=\\\\0]+$",\n "type": "string"\n },\n "type": "object"\n },\n "max_events": {\n "default": 1000,\n "description": "Stop observing and kill the process after this many recorded entries.",\n "maximum": 100000,\n "minimum": 1,\n "type": "integer"\n },\n "working_directory": {\n "description": "Value for working directory.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "breakpoints"\n ],\n "type": "object"\n },\n "kind": "native-provider",\n "name": "observe_native_calls",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "breakpoints": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "location_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "locations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "file_address": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "load_address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "module": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "symbol": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "load_address",\n "file_address",\n "module",\n "module_path",\n "symbol"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "request": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "function",\n "type": "string"\n },\n "module": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "description": "Exact symbol name, such as `open`, a C++ or mangled Swift symbol, or `-[NSString length]`.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "name",\n "module"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "class_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "objc-method",\n "type": "string"\n },\n "method_type": {\n "enum": [\n "instance",\n "class",\n "any"\n ],\n "type": "string"\n },\n "selector": {\n "description": "Selector such as `dataTaskWithRequest:completionHandler:`.",\n "minLength": 1,\n "pattern": "^[^\\\\s[\\\\]]+$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector",\n "class_name",\n "method_type"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "index",\n "request",\n "location_count",\n "locations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "event_limit_reached": {\n "type": "boolean"\n },\n "status": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n },\n "unresolved_breakpoints": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "event_limit_reached",\n "unresolved_breakpoints"\n ],\n "type": "object"\n },\n "debugger": {\n "additionalProperties": false,\n "properties": {\n "path": {\n "type": "string"\n },\n "sha256": {\n "pattern": "^[0-9a-f]{64}$",\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "sha256",\n "version"\n ],\n "type": "object"\n },\n "events": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "backtrace": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "file_address": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "load_address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "module": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "symbol": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "load_address",\n "file_address",\n "module",\n "module_path",\n "symbol"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "breakpoint_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "elapsed_ms": {\n "minimum": 0,\n "type": "number"\n },\n "file_address": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "load_address": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n },\n "module": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "receiver_class": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "registers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value": {\n "pattern": "^0x[0-9a-f]+$",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "selector": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symbol": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "thread_id": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "sequence",\n "elapsed_ms",\n "thread_id",\n "breakpoint_index",\n "load_address",\n "file_address",\n "module",\n "module_path",\n "symbol",\n "receiver_class",\n "selector",\n "registers",\n "backtrace"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "process": {\n "additionalProperties": false,\n "properties": {\n "elapsed_ms": {\n "minimum": 0,\n "type": "number"\n },\n "exit_description": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "exit_status": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "other_stops": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "outcome": {\n "enum": [\n "exited",\n "duration-elapsed",\n "event-limit",\n "stop-limit"\n ],\n "type": "string"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "stderr": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "text",\n "bytes",\n "truncated"\n ],\n "type": "object"\n },\n "stdout": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "text",\n "bytes",\n "truncated"\n ],\n "type": "object"\n },\n "terminated": {\n "type": "boolean"\n }\n },\n "required": [\n "pid",\n "outcome",\n "exit_status",\n "exit_description",\n "terminated",\n "elapsed_ms",\n "stdout",\n "stderr",\n "other_stops"\n ],\n "type": "object"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "type": "string"\n },\n "arguments": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "pattern": "^[0-9a-f]{64}$",\n "type": "string"\n },\n "working_directory": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "sha256",\n "architecture",\n "arguments",\n "working_directory"\n ],\n "type": "object"\n }\n },\n "required": [\n "target",\n "debugger",\n "process",\n "breakpoints",\n "events",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Observe Native Calls"\n },\n {\n "analysisOperation": "inspect_macho",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect Mach-O slices, load commands, imports, exports, dependencies, build metadata, segments, sections, permissions, and exact command provenance without launching Hopper.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "native-provider",\n "name": "inspect_macho",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "architectures": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "alignment": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "cpu_subtype": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "cpu_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "cpu_type",\n "cpu_subtype",\n "file_offset",\n "size",\n "alignment"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "build_metadata": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "minimum_os": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "platform": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sdk": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "tools": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "version": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "version"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "platform",\n "minimum_os",\n "sdk",\n "tools"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "dependencies": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "compatibility_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "current_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "type": "string"\n },\n "path": {\n "type": "string"\n }\n },\n "required": [\n "path",\n "kind",\n "current_version",\n "compatibility_version"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "endian": {\n "anyOf": [\n {\n "enum": [\n "little",\n "big"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "entrypoints": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "file_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "file_offset"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "exports": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "pattern": "^0x[a-fA-F0-9]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "reexport": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "source": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "weak": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "address",\n "weak",\n "reexport",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "file_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "flags": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "format": {\n "const": "mach-o",\n "type": "string"\n },\n "imports": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "pattern": "^0x[a-fA-F0-9]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "reexport": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "source": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "weak": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "address",\n "weak",\n "reexport",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "load_commands": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "fields": {\n "additionalProperties": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "type": "string"\n }\n },\n "required": [\n "index",\n "kind",\n "fields"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "provenance": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "command": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "exit": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "signal": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "signal"\n ],\n "type": "object"\n },\n "stderr_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "stdout_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "tool": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "tool",\n "command",\n "tool_version",\n "version_reason",\n "executable_sha256",\n "exit",\n "stdout_bytes",\n "stderr_bytes"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "segments": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "initial_permissions": {\n "additionalProperties": false,\n "properties": {\n "execute": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "raw": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "read": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "write": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "read",\n "write",\n "execute",\n "raw"\n ],\n "type": "object"\n },\n "maximum_permissions": {\n "additionalProperties": false,\n "properties": {\n "execute": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "raw": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "read": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "write": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "read",\n "write",\n "execute",\n "raw"\n ],\n "type": "object"\n },\n "name": {\n "type": "string"\n },\n "sections": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "pattern": "^0x[a-fA-F0-9]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "alignment": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "flags": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "segment": {\n "type": "string"\n },\n "size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "segment",\n "name",\n "address",\n "size",\n "file_offset",\n "alignment",\n "flags"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "vm_address": {\n "anyOf": [\n {\n "pattern": "^0x[a-fA-F0-9]+$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "vm_size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "vm_address",\n "vm_size",\n "file_offset",\n "file_size",\n "maximum_permissions",\n "initial_permissions",\n "sections"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "uuid": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "word_size": {\n "anyOf": [\n {\n "anyOf": [\n {\n "const": 32,\n "type": "number"\n },\n {\n "const": 64,\n "type": "number"\n }\n ]\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "format",\n "endian",\n "word_size",\n "file_type",\n "flags",\n "uuid",\n "entrypoints",\n "architectures",\n "build_metadata",\n "load_commands",\n "dependencies",\n "imports",\n "exports",\n "segments",\n "provenance",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Macho"\n },\n {\n "analysisOperation": "inspect_signature",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect the active artifact\'s code-signing identity, hashes, authorities, requirements, entitlements, hardened-runtime state, and exact command provenance.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "native-provider",\n "name": "inspect_signature",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "authorities": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "cdhashes": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "designated_requirement": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "entitlements": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "format": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "hardened_runtime": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "hash_algorithms": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "identifier": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "provenance": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "command": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "exit": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "signal": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "signal"\n ],\n "type": "object"\n },\n "stderr_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "stdout_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "tool": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "tool",\n "command",\n "tool_version",\n "version_reason",\n "executable_sha256",\n "exit",\n "stdout_bytes",\n "stderr_bytes"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "signed": {\n "type": "boolean"\n },\n "team_identifier": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "timestamp": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "signed",\n "identifier",\n "team_identifier",\n "format",\n "cdhashes",\n "hash_algorithms",\n "authorities",\n "designated_requirement",\n "entitlements",\n "timestamp",\n "hardened_runtime",\n "provenance",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Signature"\n },\n {\n "analysisOperation": "inspect_plist",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Parse Info.plist from the active artifact by default, or pass any local plist path. Returns normalized JSON rather than plutil text.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "path": "/Applications/Example.app/Contents/Info.plist"\n }\n ],\n "properties": {\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "native-provider",\n "name": "inspect_plist",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "bundle": {\n "additionalProperties": false,\n "properties": {\n "executable": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "identifier": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "short_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "identifier",\n "executable",\n "name",\n "version",\n "short_version"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "xml",\n "binary",\n "json",\n "unknown"\n ],\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "provenance": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "command": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "exit": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "signal": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "signal"\n ],\n "type": "object"\n },\n "stderr_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "stdout_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "tool": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "tool",\n "command",\n "tool_version",\n "version_reason",\n "executable_sha256",\n "exit",\n "stdout_bytes",\n "stderr_bytes"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "source_path": {\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "format",\n "value",\n "bundle",\n "source_path",\n "provenance",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Plist"\n },\n {\n "analysisOperation": "list_architectures",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "List thin or universal Mach-O slices with offsets, sizes, alignment, explicit coverage, and native-tool provenance.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "native-provider",\n "name": "list_architectures",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "architectures": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "alignment": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "cpu_subtype": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "cpu_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "cpu_type",\n "cpu_subtype",\n "file_offset",\n "size",\n "alignment"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "total": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "items",\n "total",\n "exhaustive",\n "limitations"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "provenance": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "command": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "exit": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "signal": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "signal"\n ],\n "type": "object"\n },\n "stderr_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "stdout_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "tool": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "tool",\n "command",\n "tool_version",\n "version_reason",\n "executable_sha256",\n "exit",\n "stdout_bytes",\n "stderr_bytes"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "architectures",\n "provenance",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Architectures"\n },\n {\n "analysisOperation": "demangle_swift",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Demangle an ordered list of single-line Swift symbols without requiring Hopper. Each input returns demangled, unchanged, or invalid status.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "symbols": [\n "$s4Test3fooyyF"\n ]\n }\n ],\n "properties": {\n "symbols": {\n "description": "Ordered Swift symbols to demangle.",\n "items": {\n "minLength": 1,\n "pattern": "^[^\\\\n]*$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "symbols"\n ],\n "type": "object"\n },\n "kind": "native-provider",\n "name": "demangle_swift",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "provenance": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "command": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "exit": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "signal": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "signal"\n ],\n "type": "object"\n },\n "stderr_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "stdout_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "tool": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "tool",\n "command",\n "tool_version",\n "version_reason",\n "executable_sha256",\n "exit",\n "stdout_bytes",\n "stderr_bytes"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "symbols": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "input": {\n "type": "string"\n },\n "output": {\n "type": "string"\n },\n "status": {\n "enum": [\n "demangled",\n "unchanged",\n "invalid"\n ],\n "type": "string"\n }\n },\n "required": [\n "input",\n "output",\n "status"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "symbols",\n "provenance",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Demangle Swift"\n },\n {\n "analysisOperation": "inspect_artifact",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect an active archive or application package in one call. Returns the complete content-addressed artifact graph, source Evidence, observations, relationships, integrity contradictions, hypotheses, unexplored branches, limitations, and next probes inline. Read-only DMG mounting is used automatically on supported macOS hosts. Integrity mismatches fail by default; record-and-continue keeps mismatches explicitly untrusted. It does not extract files; use extract_artifact to materialize its regular contents.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {\n "integrity_policy": {\n "default": "fail",\n "description": "Value for integrity policy.",\n "enum": [\n "fail",\n "record-and-continue"\n ],\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "artifact-provider",\n "name": "inspect_artifact",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "contradictions": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "contradiction_id": {\n "pattern": "^aic_[a-f0-9]{64}$",\n "type": "string"\n },\n "declared_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "observed_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "occurrence_id": {\n "pattern": "^occ_[a-f0-9]{64}$",\n "type": "string"\n },\n "statement": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "contradiction_id",\n "statement",\n "occurrence_id",\n "declared_sha256",\n "observed_sha256",\n "evidence_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "hypotheses_omitted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "hypotheses_retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "next_probes_omitted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "next_probes_retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "observations_omitted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "observations_retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "relationships_omitted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "relationships_retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "complete-within-substeps",\n "partial",\n "truncated"\n ],\n "type": "string"\n },\n "substeps_completed": {\n "const": 1,\n "type": "number"\n },\n "unexplored_branches_omitted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unexplored_branches_retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "substeps_completed",\n "observations_retained",\n "observations_omitted",\n "relationships_retained",\n "relationships_omitted",\n "hypotheses_retained",\n "hypotheses_omitted",\n "unexplored_branches_retained",\n "unexplored_branches_omitted",\n "next_probes_retained",\n "next_probes_omitted"\n ],\n "type": "object"\n },\n "derived_relationships": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "logical_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "occurrence_id": {\n "pattern": "^occ_[a-f0-9]{64}$",\n "type": "string"\n },\n "relation": {\n "enum": [\n "contains",\n "extracts",\n "slice-of",\n "embeds",\n "loads",\n "maps-source",\n "derived-from"\n ],\n "type": "string"\n },\n "relationship_id": {\n "pattern": "^air_[a-f0-9]{64}$",\n "type": "string"\n },\n "source_artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "relationship_id",\n "relation",\n "source_artifact_id",\n "target_artifact_id",\n "occurrence_id",\n "logical_path",\n "evidence_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "hypotheses": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "basis_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "confidence": {\n "enum": [\n "medium",\n "low"\n ],\n "type": "string"\n },\n "hypothesis_id": {\n "pattern": "^aih_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitation": {\n "minLength": 1,\n "type": "string"\n },\n "statement": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "hypothesis_id",\n "statement",\n "confidence",\n "basis_evidence_ids",\n "limitation"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "inspection_id": {\n "pattern": "^ai_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "next_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "enum": [\n "artifact-bytes",\n "managed-static-analysis",\n "native-analysis-provider",\n "ast-static-analysis"\n ],\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale",\n "authority"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "observations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "enum": [\n "root-manifest",\n "artifact",\n "occurrence",\n "integrity"\n ],\n "type": "string"\n },\n "observation_id": {\n "pattern": "^aio_[a-f0-9]{64}$",\n "type": "string"\n },\n "subject": {\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "observation_id",\n "kind",\n "subject",\n "value",\n "evidence_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "subject": {\n "additionalProperties": false,\n "properties": {\n "manifest_id": {\n "pattern": "^agm_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_format": {\n "minLength": 1,\n "type": "string"\n },\n "root_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "manifest_id",\n "root_artifact_id",\n "root_sha256",\n "root_format"\n ],\n "type": "object"\n },\n "substeps": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "operation": {\n "const": "inventory_artifact",\n "type": "string"\n },\n "status": {\n "const": "completed",\n "type": "string"\n },\n "substep_id": {\n "pattern": "^ais_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "substep_id",\n "operation",\n "status",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "unexplored_branches": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "branch_id": {\n "pattern": "^aib_[a-f0-9]{64}$",\n "type": "string"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "next_probe": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "enum": [\n "artifact-bytes",\n "managed-static-analysis",\n "native-analysis-provider",\n "ast-static-analysis"\n ],\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale",\n "authority"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "format-specific-analysis-required",\n "unknown-format"\n ],\n "type": "string"\n }\n },\n "required": [\n "branch_id",\n "reason",\n "detail",\n "next_probe",\n "evidence_id"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "inspection_id",\n "subject",\n "substeps",\n "observations",\n "derived_relationships",\n "hypotheses",\n "contradictions",\n "unexplored_branches",\n "next_probes",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Artifact"\n },\n {\n "analysisOperation": "extract_artifact",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Extract all regular files from the active archive or application package into a fresh temporary directory chosen by REA. The result includes its location. Rejects traversal and symlink escapes, never overwrites, enforces archive integrity checks, and verifies cleanup.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "artifact-provider",\n "name": "extract_artifact",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "artifacts": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "bytes_written": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "created": {\n "type": "boolean"\n },\n "relative_path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "artifact_id",\n "relative_path",\n "sha256",\n "bytes_written",\n "created"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "cleanup": {\n "additionalProperties": false,\n "properties": {\n "attempted": {\n "type": "boolean"\n },\n "residual_paths": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "verified": {\n "type": "boolean"\n }\n },\n "required": [\n "attempted",\n "verified",\n "residual_paths"\n ],\n "type": "object"\n },\n "containment_verified": {\n "type": "boolean"\n },\n "extraction_manifest": {\n "additionalProperties": false,\n "properties": {\n "extraction_id": {\n "pattern": "^aex_[a-f0-9]{64}$",\n "type": "string"\n },\n "files_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "output_root_alias": {\n "const": "$OUTPUT_ROOT",\n "type": "string"\n },\n "selected_occurrence_ids": {\n "items": {\n "pattern": "^occ_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "source_manifest_id": {\n "pattern": "^agm_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "extraction_id",\n "source_manifest_id",\n "selected_occurrence_ids",\n "files_sha256",\n "output_root_alias"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "manifest": {\n "additionalProperties": false,\n "properties": {\n "edge_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "graph_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "manifest_id": {\n "pattern": "^agm_[a-f0-9]{64}$",\n "type": "string"\n },\n "node_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "occurrence_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "root_artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_format": {\n "enum": [\n "dmg",\n "zip",\n "pkg",\n "asar",\n "javascript-bundle",\n "source-map",\n "mach-o-universal",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "hopper",\n "analysis-database",\n "ipa",\n "apk",\n "msix",\n "appx",\n "plist",\n "entitlements",\n "directory",\n "file",\n "unknown"\n ],\n "type": "string"\n },\n "root_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "manifest_id",\n "root_artifact_id",\n "root_sha256",\n "root_format",\n "graph_sha256",\n "node_count",\n "occurrence_count",\n "edge_count"\n ],\n "type": "object"\n },\n "output_root": {\n "minLength": 1,\n "type": "string"\n },\n "provenance": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "arguments": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "effects": {\n "items": {\n "enum": [\n "read",\n "write",\n "mount"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "exit_code": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "tool": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "tool",\n "arguments",\n "tool_version",\n "executable_sha256",\n "exit_code",\n "effects"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "manifest",\n "extraction_manifest",\n "output_root",\n "artifacts",\n "containment_verified",\n "cleanup",\n "provenance",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Extract Artifact"\n },\n {\n "analysisOperation": "decode_interface_builder",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Decode compiled storyboard and nib archives inside the active Apple app bundle, including keyed property lists and NIBArchive object tables, into a bounded object, hierarchy, and connection graph. This is static archive parsing; unrecognized archive classes and unresolved code handlers remain explicit unknowns.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {\n "max_connections": {\n "default": 40000,\n "description": "Maximum permitted connections for this operation.",\n "maximum": 40000,\n "minimum": 1,\n "type": "integer"\n },\n "max_documents": {\n "default": 64,\n "description": "Maximum permitted documents for this operation.",\n "maximum": 64,\n "minimum": 1,\n "type": "integer"\n },\n "max_objects": {\n "default": 20000,\n "description": "Maximum permitted objects for this operation.",\n "maximum": 20000,\n "minimum": 1,\n "type": "integer"\n }\n },\n "type": "object"\n },\n "kind": "artifact-provider",\n "name": "decode_interface_builder",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "documents": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "archive_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "connection_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "document_kind": {\n "enum": [\n "nib",\n "storyboard_scene"\n ],\n "type": "string"\n },\n "hierarchy_complete": {\n "type": "boolean"\n },\n "object_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "relative_path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "relative_path",\n "archive_sha256",\n "document_kind",\n "object_count",\n "connection_count",\n "hierarchy_complete"\n ],\n "type": "object"\n },\n "maxItems": 64,\n "type": "array"\n },\n "graph": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "examined": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "facet": {\n "minLength": 1,\n "type": "string"\n },\n "omitted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "unsupported",\n "not_requested"\n ],\n "type": "string"\n }\n },\n "required": [\n "facet",\n "status",\n "reason",\n "examined",\n "omitted"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "edges": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "from": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "relation": {\n "enum": [\n "contains",\n "outlet_to",\n "target_action",\n "segue_to",\n "objc_dispatch",\n "swift_witness_dispatch",\n "swift_vtable_dispatch",\n "thunk_to",\n "direct_call",\n "indirect_call",\n "provider_call",\n "reads",\n "writes",\n "flows_to",\n "controls"\n ],\n "type": "string"\n },\n "resolution": {\n "enum": [\n "observed",\n "inferred",\n "resolved",\n "ambiguous"\n ],\n "type": "string"\n },\n "to": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "from",\n "to",\n "relation",\n "resolution",\n "evidence",\n "limitations"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "from": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "relation": {\n "enum": [\n "contains",\n "outlet_to",\n "target_action",\n "segue_to",\n "objc_dispatch",\n "swift_witness_dispatch",\n "swift_vtable_dispatch",\n "thunk_to",\n "direct_call",\n "indirect_call",\n "provider_call",\n "reads",\n "writes",\n "flows_to",\n "controls"\n ],\n "type": "string"\n },\n "resolution": {\n "const": "unresolved",\n "type": "string"\n },\n "to": {\n "type": "null"\n }\n },\n "required": [\n "id",\n "from",\n "to",\n "relation",\n "resolution",\n "reason",\n "evidence",\n "limitations"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attributes": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "description": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "binary_metadata",\n "interface_builder_resource",\n "symbol",\n "reference",\n "decompiler"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "description",\n "location",\n "artifact_path",\n "artifact_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "enum": [\n "storyboard",\n "scene",\n "view_controller",\n "view",\n "control",\n "constraint",\n "layout_guide",\n "resource",\n "placeholder",\n "external_object",\n "outlet",\n "action",\n "segue",\n "objc_class",\n "objc_selector",\n "swift_declaration",\n "swift_requirement",\n "dispatch_table",\n "dispatch_slot",\n "thunk",\n "function",\n "state_value",\n "global_value",\n "field",\n "branch_condition",\n "sink",\n "unknown"\n ],\n "type": "string"\n },\n "location": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "file_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "kind",\n "name",\n "location",\n "attributes",\n "evidence"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "tool_version": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "version",\n "tool_version"\n ],\n "type": "object"\n },\n "target_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "target_sha256",\n "provider",\n "nodes",\n "edges",\n "coverage",\n "truncated"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "target_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "target_sha256",\n "documents",\n "graph",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Decode Interface Builder"\n },\n {\n "analysisOperation": "inspect_keyed_archive",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Inspect one Foundation NSKeyedArchiver plist in the active app bundle as original object-table nodes, class descriptors, named roots, and UID edges. Preserves shared and cyclic references without instantiating classes. The path is relative to the bundle; root selection and object pagination preserve original identities.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "path": "Contents/Resources/Model.plist"\n }\n ],\n "properties": {\n "limit": {\n "default": 20000,\n "description": "Value for limit.",\n "maximum": 20000,\n "minimum": 1,\n "type": "integer"\n },\n "offset": {\n "default": 0,\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "default": ".",\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "root": {\n "description": "Value for root.",\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "artifact-provider",\n "name": "inspect_keyed_archive",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "archive_format": {\n "enum": [\n "binary-plist",\n "xml-plist"\n ],\n "type": "string"\n },\n "archive_path": {\n "type": "string"\n },\n "archive_sha256": {\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "next_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "objects": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "class_id": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "class_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "enum": [\n "nil",\n "scalar",\n "array",\n "dictionary",\n "class-descriptor",\n "archived-object"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "observed",\n "unknown-class",\n "unresolved-class",\n "nil"\n ],\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "id",\n "kind",\n "class_id",\n "class_name",\n "status",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "path": {\n "items": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n ]\n },\n "type": "array"\n },\n "raw": {\n "$ref": "#/$defs/__schema0"\n },\n "source": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "resolved",\n "nil",\n "unresolved",\n "malformed"\n ],\n "type": "string"\n },\n "target": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "source",\n "path",\n "target",\n "status",\n "raw"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "roots": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "target_sha256": {\n "type": "string"\n },\n "total_objects": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total_references": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "target_sha256",\n "archive_path",\n "archive_sha256",\n "archive_format",\n "roots",\n "objects",\n "references",\n "total_objects",\n "total_references",\n "offset",\n "next_offset",\n "truncated",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Keyed Archive"\n },\n {\n "analysisOperation": "inspect_asset_catalog",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect compiled Assets.car metadata in an active Apple app bundle. Returns stable paginated catalog and rendition records with raw assetutil metadata and catalog digests; rendition bytes are not extracted.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "limit": 1000,\n "offset": 0\n }\n ],\n "properties": {\n "limit": {\n "default": 1000,\n "description": "Value for limit.",\n "maximum": 10000,\n "minimum": 1,\n "type": "integer"\n },\n "offset": {\n "default": 0,\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "type": "object"\n },\n "kind": "artifact-provider",\n "name": "inspect_asset_catalog",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "catalogs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "record_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "record_count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limit": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "next_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "records": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "asset_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "catalog_path": {\n "minLength": 1,\n "type": "string"\n },\n "catalog_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "enum": [\n "catalog",\n "rendition"\n ],\n "type": "string"\n },\n "metadata": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "rendition_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "catalog_path",\n "catalog_sha256",\n "index",\n "kind",\n "asset_name",\n "rendition_name",\n "metadata"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "resource_key_matches": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "field": {\n "minLength": 1,\n "type": "string"\n },\n "key": {\n "minLength": 1,\n "type": "string"\n },\n "renditions": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "catalog_path": {\n "minLength": 1,\n "type": "string"\n },\n "catalog_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "record_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "rendition_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "catalog_path",\n "catalog_sha256",\n "record_index",\n "rendition_name"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "source_archive_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_node_id": {\n "minLength": 1,\n "type": "string"\n },\n "source_path": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "enum": [\n "matched",\n "ambiguous",\n "unmatched"\n ],\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "source_path",\n "source_archive_sha256",\n "field",\n "key",\n "status",\n "renditions"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "target_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "total_records": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "target_sha256",\n "catalogs",\n "total_records",\n "offset",\n "limit",\n "records",\n "resource_key_matches",\n "next_offset",\n "truncated",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Asset Catalog"\n },\n {\n "analysisOperation": "inspect_managed_artifact",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Open and classify an explicit managed PE/CLI path, or inspect the active managed target, then inventory exact assembly/module identity, target framework evidence, references, resources, and custom attributes without loading or executing target code. Returns complete inline inventories when metadata is admitted and explicit partial or malformed coverage otherwise.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "path": "/tmp/Example.dll"\n }\n ],\n "properties": {\n "path": {\n "description": "Explicit managed PE/CLI path; omit to reuse the previously selected managed target",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "managed-provider",\n "name": "inspect_managed_artifact",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "format": {\n "const": "pe",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "byte_length",\n "format"\n ],\n "type": "object"\n },\n "assembly": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "culture": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "flags": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "hash_algorithm": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "name": {\n "type": "string"\n },\n "public_key": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "enum": [\n "none",\n "public-key",\n "public-key-token"\n ],\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "token": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{16}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "byte_length",\n "sha256",\n "token"\n ],\n "type": "object"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "version": {\n "pattern": "^\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+$",\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "culture",\n "flags",\n "hash_algorithm",\n "public_key",\n "token",\n "row_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "attributes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "constructor_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "decoded_fixed_string": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "parent_token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "type_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "value_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "value_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "parent_token",\n "constructor_token",\n "type_name",\n "value_length",\n "value_sha256",\n "decoded_fixed_string",\n "token",\n "row_offset"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "classification": {\n "additionalProperties": false,\n "properties": {\n "container": {\n "const": "pe",\n "type": "string"\n },\n "evidence": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "minLength": 1,\n "type": "string"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "detail",\n "file_offset"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "implementation": {\n "enum": [\n "cil",\n "cil-and-ready-to-run",\n "cpp-cli-mixed",\n "metadata-only",\n "not-managed",\n "unknown"\n ],\n "type": "string"\n },\n "managed_architecture": {\n "enum": [\n "anycpu",\n "anycpu-prefer-32",\n "x86",\n "x86_64",\n "arm",\n "arm64",\n "unknown"\n ],\n "type": "string"\n },\n "runtime_family": {\n "enum": [\n "dotnet-framework",\n "modern-dotnet",\n "unity-mono",\n "mixed-clr-native",\n "unknown"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "managed",\n "not-managed",\n "malformed"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "container",\n "runtime_family",\n "implementation",\n "managed_architecture",\n "evidence"\n ],\n "type": "object"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "issues": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "invalid-cli-header",\n "invalid-directory",\n "invalid-metadata-root",\n "invalid-stream",\n "missing-stream",\n "invalid-tables",\n "invalid-row",\n "invalid-heap-index",\n "invalid-string",\n "invalid-blob",\n "unsupported-signature",\n "invalid-guid",\n "invalid-resource"\n ],\n "type": "string"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "code",\n "scope",\n "offset",\n "detail"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "state": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "issues"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "metadata": {\n "additionalProperties": false,\n "properties": {\n "status": {\n "enum": [\n "absent",\n "complete",\n "partial",\n "malformed"\n ],\n "type": "string"\n },\n "stream_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "table_row_counts": {\n "additionalProperties": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "status",\n "version",\n "stream_names",\n "table_row_counts"\n ],\n "type": "object"\n },\n "module": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "enc_base_id": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "enc_id": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "generation": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "name",\n "generation",\n "mvid",\n "enc_id",\n "enc_base_id",\n "token",\n "row_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "pe": {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n "characteristics": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "cli": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "entry_point": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "metadata-token",\n "native-rva",\n "none"\n ],\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n "flag_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "flags": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "header_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "header_size": {\n "maximum": 9007199254740991,\n "minimum": 72,\n "type": "integer"\n },\n "managed_native_header_rva": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "managed_native_header_size": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "metadata_rva": {\n "maximum": 4294967295,\n "minimum": 1,\n "type": "integer"\n },\n "metadata_size": {\n "maximum": 4294967295,\n "minimum": 1,\n "type": "integer"\n },\n "ready_to_run_signature": {\n "type": "boolean"\n },\n "resources_rva": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "resources_size": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "runtime_version": {\n "type": "string"\n },\n "strong_name_rva": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "strong_name_size": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "header_offset",\n "header_size",\n "runtime_version",\n "flags",\n "flag_names",\n "entry_point",\n "metadata_rva",\n "metadata_size",\n "resources_rva",\n "resources_size",\n "strong_name_rva",\n "strong_name_size",\n "managed_native_header_rva",\n "managed_native_header_size",\n "ready_to_run_signature"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "machine": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "machine_hex": {\n "pattern": "^0x[0-9a-f]{4}$",\n "type": "string"\n },\n "optional_header": {\n "enum": [\n "pe32",\n "pe32-plus"\n ],\n "type": "string"\n },\n "section_count": {\n "maximum": 65535,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "machine",\n "machine_hex",\n "architecture",\n "optional_header",\n "section_count",\n "characteristics",\n "cli"\n ],\n "type": "object"\n },\n "references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "culture": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "flags": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "hash_value_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "hash_value_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "public_key_or_token": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "enum": [\n "none",\n "public-key",\n "public-key-token"\n ],\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "token": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{16}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "byte_length",\n "sha256",\n "token"\n ],\n "type": "object"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "version": {\n "pattern": "^\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+$",\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "culture",\n "flags",\n "public_key_or_token",\n "hash_value_sha256",\n "hash_value_length",\n "token",\n "row_offset"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "resources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "data_length": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "data_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "declared_offset": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "embedded": {\n "type": "boolean"\n },\n "flags": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "implementation_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "visibility": {\n "enum": [\n "public",\n "private",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "name",\n "flags",\n "visibility",\n "implementation_token",\n "embedded",\n "declared_offset",\n "data_length",\n "data_sha256",\n "token",\n "row_offset"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "target_frameworks": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "artifact",\n "pe",\n "classification",\n "metadata",\n "module",\n "assembly",\n "target_frameworks",\n "references",\n "resources",\n "attributes",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Managed Artifact"\n },\n {\n "analysisOperation": "inspect_managed_members",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Inspect PE/CLI metadata members, signatures, raw CIL hashes, decoded-instruction-tuple hashes, separately reported exception regions, call edges, and field-access anchors without loading or executing target code. Metadata tokens are reported as build-local coordinates bound to the artifact SHA-256 and MVID; the tuple hash does not resolve them or fully commit control flow.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "path": {\n "description": "Explicit managed PE/CLI path; omit to reuse the previously selected managed target",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "managed-provider",\n "name": "inspect_managed_members",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "format": {\n "const": "pe",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "byte_length",\n "format"\n ],\n "type": "object"\n },\n "call_edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "caller": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "caller_token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "opcode": {\n "type": "string"\n },\n "target_kind": {\n "enum": [\n "method-def",\n "member-ref",\n "method-spec",\n "unknown"\n ],\n "type": "string"\n },\n "target_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "caller_token",\n "caller",\n "opcode",\n "target_token",\n "target_kind",\n "target_name"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "issues": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "invalid-cli-header",\n "invalid-directory",\n "invalid-metadata-root",\n "invalid-stream",\n "missing-stream",\n "invalid-tables",\n "invalid-row",\n "invalid-heap-index",\n "invalid-string",\n "invalid-blob",\n "unsupported-signature",\n "invalid-guid",\n "invalid-resource"\n ],\n "type": "string"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "code",\n "scope",\n "offset",\n "detail"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "state": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "issues"\n ],\n "type": "object"\n },\n "field_accesses": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "field_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "field_token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "method": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "method_token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "opcode": {\n "type": "string"\n }\n },\n "required": [\n "method_token",\n "method",\n "opcode",\n "field_token",\n "field_name"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "fields": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "declaring_type_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "flags": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "name": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "signature": {\n "additionalProperties": false,\n "properties": {\n "calling_convention": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "field_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "generic_parameter_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "issue": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "method",\n "field",\n "unknown"\n ],\n "type": "string"\n },\n "parameter_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "parameter_types": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "parse_status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "malformed"\n ],\n "type": "string"\n },\n "raw_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "raw_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "return_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "raw_length",\n "raw_sha256",\n "kind",\n "parse_status",\n "calling_convention",\n "generic_parameter_count",\n "parameter_count",\n "return_type",\n "parameter_types",\n "field_type",\n "issue"\n ],\n "type": "object"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "row_offset",\n "declaring_type_token",\n "declaring_type",\n "name",\n "flags",\n "signature"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "identity_scope": {\n "additionalProperties": false,\n "properties": {\n "requires_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "requires_mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "token_identity": {\n "const": "build-local",\n "type": "string"\n }\n },\n "required": [\n "token_identity",\n "requires_artifact_sha256",\n "requires_mvid"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "member_refs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "parent_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "signature": {\n "additionalProperties": false,\n "properties": {\n "calling_convention": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "field_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "generic_parameter_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "issue": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "method",\n "field",\n "unknown"\n ],\n "type": "string"\n },\n "parameter_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "parameter_types": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "parse_status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "malformed"\n ],\n "type": "string"\n },\n "raw_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "raw_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "return_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "raw_length",\n "raw_sha256",\n "kind",\n "parse_status",\n "calling_convention",\n "generic_parameter_count",\n "parameter_count",\n "return_type",\n "parameter_types",\n "field_type",\n "issue"\n ],\n "type": "object"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "row_offset",\n "parent_token",\n "name",\n "signature"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "metadata": {\n "additionalProperties": false,\n "properties": {\n "status": {\n "enum": [\n "absent",\n "complete",\n "partial",\n "malformed"\n ],\n "type": "string"\n },\n "table_row_counts": {\n "additionalProperties": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "status",\n "version",\n "table_row_counts"\n ],\n "type": "object"\n },\n "methods": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "body": {\n "additionalProperties": false,\n "properties": {\n "anchors": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "il_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "opcode": {\n "minLength": 1,\n "type": "string"\n },\n "operand": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "operand_kind": {\n "enum": [\n "none",\n "branch",\n "constant",\n "field",\n "method",\n "signature",\n "string",\n "switch",\n "token",\n "type",\n "variable",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "il_offset",\n "opcode",\n "operand_kind",\n "operand"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "decoded_instruction_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "exception_regions": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "class_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "filter_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "flags": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "handler_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "handler_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "try_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "try_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "flags",\n "try_offset",\n "try_length",\n "handler_offset",\n "handler_length",\n "class_token",\n "filter_offset"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "file_offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "header_format": {\n "enum": [\n "tiny",\n "fat",\n "none",\n "unknown"\n ],\n "type": "string"\n },\n "il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "SHA-256 of exactly the raw il_size CIL bytes; excludes the method header, locals signature, alignment, and extra sections"\n },\n "il_size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "init_locals": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "instruction_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "issue": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "local_var_sig_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "max_stack": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "normalized_il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Member-result-v1 SHA-256 of UTF-8 JSON [opcode, operand_kind, operand] tuples for a complete decode; retains build-local tokens, omits switch targets, and excludes headers, locals, and exception regions"\n },\n "opcode_counts": {\n "additionalProperties": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "rva": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "present",\n "partial",\n "absent",\n "malformed"\n ],\n "type": "string"\n },\n "truncated_instructions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "header_format",\n "rva",\n "file_offset",\n "max_stack",\n "init_locals",\n "local_var_sig_token",\n "il_size",\n "il_sha256",\n "normalized_il_sha256",\n "instruction_count",\n "decoded_instruction_count",\n "truncated_instructions",\n "opcode_counts",\n "anchors",\n "exception_regions",\n "issue"\n ],\n "type": "object"\n },\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "declaring_type_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "flags": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "impl_flags": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "name": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "rva": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "signature": {\n "additionalProperties": false,\n "properties": {\n "calling_convention": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "field_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "generic_parameter_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "issue": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "method",\n "field",\n "unknown"\n ],\n "type": "string"\n },\n "parameter_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "parameter_types": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "parse_status": {\n "enum": [\n "decoded",\n "partial",\n "unsupported",\n "malformed"\n ],\n "type": "string"\n },\n "raw_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "raw_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "return_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "raw_length",\n "raw_sha256",\n "kind",\n "parse_status",\n "calling_convention",\n "generic_parameter_count",\n "parameter_count",\n "return_type",\n "parameter_types",\n "field_type",\n "issue"\n ],\n "type": "object"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "row_offset",\n "declaring_type_token",\n "declaring_type",\n "name",\n "rva",\n "impl_flags",\n "flags",\n "signature",\n "body"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "module": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "enc_base_id": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "enc_id": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "generation": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "name",\n "generation",\n "mvid",\n "enc_id",\n "enc_base_id",\n "token",\n "row_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "types": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "extends_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "field_list": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "first_row": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "last_row": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "first_row",\n "last_row",\n "count"\n ],\n "type": "object"\n },\n "flags": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "full_name": {\n "type": "string"\n },\n "method_list": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "first_row": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "last_row": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "first_row",\n "last_row",\n "count"\n ],\n "type": "object"\n },\n "name": {\n "type": "string"\n },\n "namespace": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "row_offset",\n "namespace",\n "name",\n "full_name",\n "flags",\n "extends_token",\n "field_list",\n "method_list"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "artifact",\n "module",\n "metadata",\n "identity_scope",\n "types",\n "fields",\n "methods",\n "member_refs",\n "call_edges",\n "field_accesses",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Managed Members"\n },\n {\n "analysisOperation": "inspect_managed_native_boundaries",\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Inspect PE/CLI ModuleRef, ImplMap/PInvoke declarations, CLI native-header indicators, and non-IL method implementation flags without loading or executing target code. Results are managed declarations and degraded native-boundary observations, not verified native exports or addresses.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "path": {\n "description": "Explicit managed PE/CLI path; omit to reuse the previously selected managed target",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "managed-provider",\n "name": "inspect_managed_native_boundaries",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "format": {\n "const": "pe",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "byte_length",\n "format"\n ],\n "type": "object"\n },\n "cli_native": {\n "additionalProperties": false,\n "properties": {\n "il_only": {\n "type": "boolean"\n },\n "managed_native_header_rva": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "managed_native_header_size": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "native_entry_point": {\n "type": "boolean"\n },\n "ready_to_run_signature": {\n "type": "boolean"\n },\n "requires_32bit": {\n "type": "boolean"\n },\n "strong_name_signed": {\n "type": "boolean"\n }\n },\n "required": [\n "il_only",\n "requires_32bit",\n "strong_name_signed",\n "native_entry_point",\n "ready_to_run_signature",\n "managed_native_header_rva",\n "managed_native_header_size"\n ],\n "type": "object"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "issues": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "invalid-cli-header",\n "invalid-directory",\n "invalid-metadata-root",\n "invalid-stream",\n "missing-stream",\n "invalid-tables",\n "invalid-row",\n "invalid-heap-index",\n "invalid-string",\n "invalid-blob",\n "unsupported-signature",\n "invalid-guid",\n "invalid-resource"\n ],\n "type": "string"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "code",\n "scope",\n "offset",\n "detail"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "state": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "issues"\n ],\n "type": "object"\n },\n "identity_scope": {\n "additionalProperties": false,\n "properties": {\n "requires_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "requires_mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "token_identity": {\n "const": "build-local",\n "type": "string"\n }\n },\n "required": [\n "token_identity",\n "requires_artifact_sha256",\n "requires_mvid"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "metadata": {\n "additionalProperties": false,\n "properties": {\n "status": {\n "enum": [\n "absent",\n "complete",\n "partial",\n "malformed"\n ],\n "type": "string"\n },\n "table_row_counts": {\n "additionalProperties": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "status",\n "version",\n "table_row_counts"\n ],\n "type": "object"\n },\n "module": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "enc_base_id": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "enc_id": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "generation": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "name",\n "generation",\n "mvid",\n "enc_id",\n "enc_base_id",\n "token",\n "row_offset"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_refs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "row_offset",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "native_implementations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "body_interpretation": {\n "enum": [\n "managed-cil",\n "native-or-runtime",\n "not-file-backed",\n "unknown"\n ],\n "type": "string"\n },\n "boundary_kind": {\n "enum": [\n "pinvoke",\n "native-body",\n "runtime-provided",\n "unmanaged-method",\n "mixed-or-unknown"\n ],\n "type": "string"\n },\n "code_type": {\n "enum": [\n "il",\n "native",\n "optil",\n "runtime",\n "unknown"\n ],\n "type": "string"\n },\n "flags": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "impl_flags": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "managed_kind": {\n "enum": [\n "managed",\n "unmanaged",\n "unknown"\n ],\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "pinvoke_declared": {\n "type": "boolean"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "rva": {\n "maximum": 4294967295,\n "minimum": 0,\n "type": "integer"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "row_offset",\n "name",\n "rva",\n "flags",\n "impl_flags",\n "code_type",\n "managed_kind",\n "pinvoke_declared",\n "boundary_kind",\n "body_interpretation"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "pinvoke_imports": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "best_fit": {\n "enum": [\n "assembly-default",\n "enabled",\n "disabled",\n "unknown"\n ],\n "type": "string"\n },\n "call_convention": {\n "enum": [\n "not-specified",\n "winapi",\n "cdecl",\n "stdcall",\n "thiscall",\n "fastcall",\n "unknown"\n ],\n "type": "string"\n },\n "char_set": {\n "enum": [\n "not-specified",\n "ansi",\n "unicode",\n "auto",\n "unknown"\n ],\n "type": "string"\n },\n "import_name": {\n "type": "string"\n },\n "import_scope_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "import_scope_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "mapping_flags": {\n "maximum": 65535,\n "minimum": 0,\n "type": "integer"\n },\n "mapping_flags_hex": {\n "pattern": "^0x[0-9a-f]{4}$",\n "type": "string"\n },\n "member_kind": {\n "enum": [\n "method",\n "field",\n "unknown"\n ],\n "type": "string"\n },\n "member_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "no_mangle": {\n "type": "boolean"\n },\n "row_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "supports_last_error": {\n "type": "boolean"\n },\n "throw_on_unmappable_char": {\n "enum": [\n "assembly-default",\n "enabled",\n "disabled",\n "unknown"\n ],\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "verification": {\n "const": "managed-declaration-only",\n "type": "string"\n }\n },\n "required": [\n "token",\n "row_offset",\n "mapping_flags",\n "mapping_flags_hex",\n "member_token",\n "member_kind",\n "member_name",\n "import_name",\n "import_scope_token",\n "import_scope_name",\n "no_mangle",\n "char_set",\n "call_convention",\n "supports_last_error",\n "best_fit",\n "throw_on_unmappable_char",\n "verification"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "mixed_mode_or_native_header": {\n "type": "boolean"\n },\n "module_ref_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "native_implementation_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "pinvoke_import_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "ready_to_run": {\n "type": "boolean"\n }\n },\n "required": [\n "module_ref_count",\n "pinvoke_import_count",\n "native_implementation_count",\n "ready_to_run",\n "mixed_mode_or_native_header"\n ],\n "type": "object"\n }\n },\n "required": [\n "artifact",\n "module",\n "metadata",\n "identity_scope",\n "cli_native",\n "module_refs",\n "pinvoke_imports",\n "native_implementations",\n "summary",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Managed Native Boundaries"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect a local APK\'s package, SDK and permission declarations, decoded manifest and class/resource counts. Does not execute the APK or verify signatures. Requires a caller-supplied JADX MCP JAR and JDK; uses one worker in an isolated temporary workspace.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "path": "/tmp/Example.apk"\n }\n ],\n "properties": {\n "path": {\n "description": "Local APK path; the target is never executed",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path"\n ],\n "type": "object"\n },\n "kind": "android-provider",\n "name": "inspect_android_package",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "class_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "heap_limit_mib": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "source_revision": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "worker_count": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "version",\n "artifact_sha256",\n "source_revision",\n "worker_count",\n "heap_limit_mib"\n ],\n "type": "object"\n },\n "manifest": {\n "additionalProperties": false,\n "properties": {\n "reported_total_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "status",\n "text",\n "reported_total_bytes"\n ],\n "type": "object"\n },\n "min_sdk": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "package_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "permissions": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "resource_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "signature_verification": {\n "const": "not_performed",\n "type": "string"\n },\n "target_sdk": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version_code": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "engine",\n "package_name",\n "version_name",\n "version_code",\n "min_sdk",\n "target_sdk",\n "permissions",\n "manifest",\n "class_count",\n "resource_count",\n "signature_verification"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Android Package"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Search complete decompiler class names in an explicit local APK by case-sensitive substring. Empty query inventories all classes. Retains artifact and engine identity; no APK execution.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "path": "/tmp/Example.apk",\n "query": "Activity"\n }\n ],\n "properties": {\n "path": {\n "description": "Local APK path; the target is never executed",\n "minLength": 1,\n "type": "string"\n },\n "query": {\n "description": "Case-sensitive class-name substring; empty lists all classes",\n "type": "string"\n }\n },\n "required": [\n "path",\n "query"\n ],\n "type": "object"\n },\n "kind": "android-provider",\n "name": "search_android_classes",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "const": "complete",\n "type": "string"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "heap_limit_mib": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "source_revision": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "worker_count": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "version",\n "artifact_sha256",\n "source_revision",\n "worker_count",\n "heap_limit_mib"\n ],\n "type": "object"\n },\n "matches": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "query": {\n "type": "string"\n },\n "total_classes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "engine",\n "query",\n "total_classes",\n "matches",\n "coverage"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Search Android Classes"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect one exact Android class\'s parsed methods, fields and inner classes, including synthetic members. Returns display signatures and explicit unknown DEX descriptors without generating source. Overload indices remain consistent within the artifact and engine session.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "class_name": "example.MainActivity",\n "path": "/tmp/Example.apk"\n }\n ],\n "properties": {\n "class_name": {\n "description": "Exact fully qualified class name",\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "description": "Local APK path; the target is never executed",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "class_name"\n ],\n "type": "object"\n },\n "kind": "android-provider",\n "name": "inspect_android_class",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "class_name": {\n "type": "string"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "heap_limit_mib": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "source_revision": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "worker_count": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "version",\n "artifact_sha256",\n "source_revision",\n "worker_count",\n "heap_limit_mib"\n ],\n "type": "object"\n },\n "fields": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "reported_type": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "reported_type"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "inner_classes": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "methods": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "dex_descriptor": {\n "type": "null"\n },\n "is_constructor": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "overload_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reported_signature": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "reported_signature",\n "overload_index",\n "is_constructor",\n "dex_descriptor"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "engine",\n "class_name",\n "methods",\n "fields",\n "inner_classes"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Android Class"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Decompile one Android method with smali fallback. Requires explicit overload_index when multiple methods share a name. Reports representation, fallback markers and source truncation; never treats display signatures as exact DEX identities.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "class_name": "example.MainActivity",\n "method_name": "onCreate",\n "path": "/tmp/Example.apk"\n }\n ],\n "properties": {\n "class_name": {\n "description": "Exact fully qualified class name",\n "minLength": 1,\n "type": "string"\n },\n "method_name": {\n "description": "Value for method name.",\n "minLength": 1,\n "type": "string"\n },\n "overload_index": {\n "description": "Explicit index among same-name methods; required when ambiguous",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "description": "Local APK path; the target is never executed",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "class_name",\n "method_name"\n ],\n "type": "object"\n },\n "kind": "android-provider",\n "name": "inspect_android_method",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "body_status": {\n "enum": [\n "available",\n "not_available"\n ],\n "type": "string"\n },\n "class_name": {\n "type": "string"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "heap_limit_mib": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "source_revision": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "worker_count": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "version",\n "artifact_sha256",\n "source_revision",\n "worker_count",\n "heap_limit_mib"\n ],\n "type": "object"\n },\n "fell_back": {\n "type": "boolean"\n },\n "markers": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "method": {\n "additionalProperties": false,\n "properties": {\n "dex_descriptor": {\n "type": "null"\n },\n "is_constructor": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "overload_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reported_signature": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "reported_signature",\n "overload_index",\n "is_constructor",\n "dex_descriptor"\n ],\n "type": "object"\n },\n "overload_count": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "representation": {\n "enum": [\n "java",\n "smali"\n ],\n "type": "string"\n },\n "source": {\n "additionalProperties": false,\n "properties": {\n "reported_total_bytes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "status",\n "text",\n "reported_total_bytes"\n ],\n "type": "object"\n }\n },\n "required": [\n "engine",\n "class_name",\n "method",\n "overload_count",\n "representation",\n "body_status",\n "fell_back",\n "markers",\n "source"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Android Method"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Trace incoming static references to an exact Android class or unique method. Overloaded method reference queries are explicitly unsupported by the current engine. Source lines and DEX offsets remain unknown; no runtime calls are inferred.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "class_name": "example.MainActivity",\n "path": "/tmp/Example.apk"\n }\n ],\n "properties": {\n "class_name": {\n "description": "Exact fully qualified class name",\n "minLength": 1,\n "type": "string"\n },\n "method_name": {\n "description": "Omit for class references; ambiguous overloaded methods are unsupported",\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "description": "Local APK path; the target is never executed",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "class_name"\n ],\n "type": "object"\n },\n "kind": "android-provider",\n "name": "trace_android_references",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "class_name": {\n "type": "string"\n },\n "coverage": {\n "const": "complete",\n "type": "string"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "heap_limit_mib": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "source_revision": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "worker_count": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "version",\n "artifact_sha256",\n "source_revision",\n "worker_count",\n "heap_limit_mib"\n ],\n "type": "object"\n },\n "method_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "containing_class": {\n "type": "string"\n },\n "dex_offset": {\n "type": "null"\n },\n "kind": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "reported_full_name": {\n "type": "string"\n },\n "source_line": {\n "type": "null"\n },\n "top_class": {\n "type": "string"\n }\n },\n "required": [\n "kind",\n "name",\n "reported_full_name",\n "containing_class",\n "top_class",\n "source_line",\n "dex_offset"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "engine",\n "class_name",\n "method_name",\n "total",\n "references",\n "coverage"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Android References"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect signature candidates in a local firmware file with Binwalk 3.1.x (verified with 3.1.0). Returns offsets, reported lengths, descriptions and raw confidence values. Length validation and runtime addresses remain unknown. No extraction or target execution. Linux, caller-supplied tool and util-linux prlimit required; 128 MiB input limit, one worker and 120-second deadline.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "path": "/tmp/firmware.bin"\n }\n ],\n "properties": {\n "path": {\n "description": "Local firmware file; never executed",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path"\n ],\n "type": "object"\n },\n "kind": "firmware-provider",\n "name": "inspect_firmware_regions",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "const": "signature_scan",\n "type": "string"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "executable_path": {\n "type": "string"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "source_revision": {\n "type": "null"\n },\n "version": {\n "type": "string"\n },\n "worker_count": {\n "const": 1,\n "type": "number"\n }\n },\n "required": [\n "name",\n "version",\n "executable_path",\n "executable_sha256",\n "source_revision",\n "worker_count"\n ],\n "type": "object"\n },\n "input_size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "regions": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "description": {\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "provider_id": {\n "type": "string"\n },\n "reported_confidence": {\n "maximum": 255,\n "minimum": 0,\n "type": "integer"\n },\n "reported_size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "signature": {\n "type": "string"\n },\n "size_basis": {\n "const": "provider_reported_validation_unknown",\n "type": "string"\n }\n },\n "required": [\n "provider_id",\n "offset",\n "reported_size",\n "signature",\n "description",\n "reported_confidence",\n "size_basis"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "engine",\n "input_size",\n "regions",\n "coverage"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Firmware Regions"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Extract caller-selected firmware bytes with Unblob 26.6.x (verified with 26.6.4) into an absent output directory. Returns verified child paths/digests, task derivations, unknown chunks and extraction diagnostics inline. Preserves the default sandbox; publishes regular files only. Linux, caller-supplied tool, format-specific extractors and util-linux prlimit required. No mounts or extracted-code execution. Select a returned native file with open_binary for Ghidra analysis; decompressed offsets are not original-file addresses.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "output_directory": "/tmp/firmware-output",\n "path": "/tmp/firmware.bin",\n "range": {\n "length": 4096,\n "offset": 64\n }\n }\n ],\n "properties": {\n "max_depth": {\n "default": 3,\n "description": "Extraction depth; reaching it yields partial analysis",\n "maximum": 10,\n "minimum": 1,\n "type": "integer"\n },\n "max_output_bytes": {\n "default": 268435456,\n "description": "Staging file-byte budget, checked during extraction; transient overshoot is possible",\n "maximum": 536870912,\n "minimum": 1,\n "type": "integer"\n },\n "max_output_files": {\n "default": 10000,\n "description": "Staging entry budget, including directories and links",\n "maximum": 100000,\n "minimum": 1,\n "type": "integer"\n },\n "output_directory": {\n "description": "Absolute, absent output directory; parent must exist",\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "description": "Local firmware file; never executed",\n "minLength": 1,\n "type": "string"\n },\n "range": {\n "additionalProperties": false,\n "description": "Explicit file-byte interval; omit to extract the whole file",\n "properties": {\n "length": {\n "description": "Value for length.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "offset",\n "length"\n ],\n "type": "object"\n }\n },\n "required": [\n "path",\n "output_directory"\n ],\n "type": "object"\n },\n "kind": "firmware-provider",\n "name": "extract_firmware",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "chunks": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "encrypted": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "handler": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "input_path": {\n "type": "string"\n },\n "provider_id": {\n "type": "string"\n },\n "range": {\n "additionalProperties": false,\n "properties": {\n "length": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "offset",\n "length"\n ],\n "type": "object"\n },\n "root_file_range": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "length": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "offset",\n "length"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "input_path",\n "provider_id",\n "range",\n "handler",\n "encrypted",\n "root_file_range"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "coverage": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n },\n "depth_limited_paths": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "derivations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "child_path": {\n "type": "string"\n },\n "handler": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "parent_file_range": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "length": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "offset",\n "length"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "parent_path": {\n "type": "string"\n },\n "provider_blob_id": {\n "type": "string"\n }\n },\n "required": [\n "parent_path",\n "child_path",\n "provider_blob_id",\n "handler",\n "parent_file_range"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "diagnostics": {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "executable_path": {\n "type": "string"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "source_revision": {\n "type": "null"\n },\n "version": {\n "type": "string"\n },\n "worker_count": {\n "const": 1,\n "type": "number"\n }\n },\n "required": [\n "name",\n "version",\n "executable_path",\n "executable_sha256",\n "source_revision",\n "worker_count"\n ],\n "type": "object"\n },\n "files": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_depth": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "mime_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "original_file_range": {\n "type": "null"\n },\n "path": {\n "type": "string"\n },\n "relative_path": {\n "type": "string"\n },\n "reported_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "runtime_address": {\n "type": "null"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "relative_path",\n "path",\n "size",\n "sha256",\n "reported_sha256",\n "mime_type",\n "analysis_depth",\n "runtime_address",\n "original_file_range"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "output_directory": {\n "type": "string"\n },\n "published_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "selection": {\n "additionalProperties": false,\n "properties": {\n "length": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "offset",\n "length",\n "sha256"\n ],\n "type": "object"\n },\n "unpublished_entries": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "symlink",\n "special"\n ],\n "type": "string"\n },\n "link_target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "relative_path": {\n "type": "string"\n }\n },\n "required": [\n "relative_path",\n "kind",\n "link_target"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "engine",\n "selection",\n "output_directory",\n "coverage",\n "files",\n "unpublished_entries",\n "chunks",\n "derivations",\n "depth_limited_paths",\n "diagnostics",\n "published_bytes"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Extract Firmware"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare two authenticated inspect_managed_members Evidence records using unique-only tiers: exact CIL/signature, exact declared type, name, and raw signature (decoded or not), and decoded structural method shape. Names alone are never a matching basis; a member whose signature was not decoded, or whose same-named counterpart\'s was not, stays unknown rather than added or removed. Metadata tokens remain build-local coordinates bound to each artifact SHA-256 and MVID, and the tuple digest does not itself remap them.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "left": {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_b3525a33f9aecdcfeac8372301c13cb9153b189d93ef337eee0fef9425171ce3",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "artifact": {\n "byte_length": 4096,\n "format": "pe",\n "path": "/examples/00000000.dll",\n "sha256": "0000000000000000000000000000000000000000000000000000000000000000"\n },\n "call_edges": [],\n "coverage": {\n "issues": [],\n "state": "complete"\n },\n "field_accesses": [],\n "fields": [],\n "identity_scope": {\n "requires_artifact_sha256": "0000000000000000000000000000000000000000000000000000000000000000",\n "requires_mvid": "00112233-4455-6677-8899-aabbccddeeff",\n "token_identity": "build-local"\n },\n "limitations": [],\n "member_refs": [],\n "metadata": {\n "status": "complete",\n "table_row_counts": {},\n "version": "v4.0.30319"\n },\n "methods": [],\n "module": {\n "enc_base_id": null,\n "enc_id": null,\n "generation": 0,\n "mvid": "00112233-4455-6677-8899-aabbccddeeff",\n "name": "Example.dll",\n "row_offset": 0,\n "token": "0x00000001"\n },\n "types": []\n },\n "operation": "inspect_managed_members",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n },\n "right": {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_8f79f99bceb5bf3d5e5af7fd678ac07b67427756eca0784cd56ffa6b9c4dbda4",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "artifact": {\n "byte_length": 4096,\n "format": "pe",\n "path": "/examples/11111111.dll",\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "call_edges": [],\n "coverage": {\n "issues": [],\n "state": "complete"\n },\n "field_accesses": [],\n "fields": [],\n "identity_scope": {\n "requires_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "requires_mvid": "ffeeddcc-bbaa-4988-9766-554433221100",\n "token_identity": "build-local"\n },\n "limitations": [],\n "member_refs": [],\n "metadata": {\n "status": "complete",\n "table_row_counts": {},\n "version": "v4.0.30319"\n },\n "methods": [],\n "module": {\n "enc_base_id": null,\n "enc_id": null,\n "generation": 0,\n "mvid": "ffeeddcc-bbaa-4988-9766-554433221100",\n "name": "Example.dll",\n "row_offset": 0,\n "token": "0x00000001"\n },\n "types": []\n },\n "operation": "inspect_managed_members",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n }\n }\n ],\n "properties": {\n "left": {\n "additionalProperties": false,\n "description": "Left-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "description": "Right-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "left",\n "right"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "compare_managed_members",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "const": "rea-managed-member-comparison",\n "type": "string"\n },\n "name_matching": {\n "const": "exact-signature-fallback",\n "type": "string"\n },\n "token_identity": {\n "const": "build-local",\n "type": "string"\n }\n },\n "required": [\n "name",\n "token_identity",\n "name_matching"\n ],\n "type": "object"\n },\n "comparison_id": {\n "pattern": "^mmc_[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "left_status": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n },\n "right_status": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "complete-within-inputs",\n "partial"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "left_status",\n "right_status"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "fields": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_field_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "exact-il-signature",\n "exact-signature",\n "structural-method-shape",\n "field-signature"\n ],\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "enum": [\n "exact",\n "high"\n ],\n "type": "string"\n },\n "status": {\n "const": "matched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256"\n ],\n "type": "object"\n },\n "status": {\n "enum": [\n "unchanged",\n "changed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_field_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "type": "null"\n },\n "status": {\n "enum": [\n "removed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_field_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256"\n ],\n "type": "object"\n },\n "status": {\n "enum": [\n "added",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_field_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "exact-il-signature",\n "exact-signature",\n "structural-method-shape",\n "field-signature"\n ],\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "ambiguous",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "type": "null"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "left": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "fields_total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "metadata_status": {\n "enum": [\n "absent",\n "complete",\n "partial",\n "malformed"\n ],\n "type": "string"\n },\n "methods_total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "artifact_sha256",\n "mvid",\n "module_name",\n "metadata_status",\n "methods_total",\n "fields_total"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "matching": {\n "additionalProperties": false,\n "properties": {\n "ambiguous": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "exact_il_signature": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "exact_signature": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "field_signature": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "structural_method_shape": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unmatched": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "exact_il_signature",\n "exact_signature",\n "structural_method_shape",\n "field_signature",\n "ambiguous",\n "unmatched"\n ],\n "type": "object"\n },\n "methods": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "metadata",\n "signature",\n "cil",\n "opcode-shape",\n "call-shape",\n "field-shape",\n "exception-shape",\n "availability",\n "body-coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_method_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "normalized_il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256",\n "normalized_il_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "exact-il-signature",\n "exact-signature",\n "structural-method-shape",\n "field-signature"\n ],\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "enum": [\n "exact",\n "high"\n ],\n "type": "string"\n },\n "status": {\n "const": "matched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "normalized_il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256",\n "normalized_il_sha256"\n ],\n "type": "object"\n },\n "status": {\n "enum": [\n "unchanged",\n "changed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "dimensions",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "metadata",\n "signature",\n "cil",\n "opcode-shape",\n "call-shape",\n "field-shape",\n "exception-shape",\n "availability",\n "body-coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_method_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "normalized_il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256",\n "normalized_il_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "type": "null"\n },\n "status": {\n "enum": [\n "removed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "dimensions",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "metadata",\n "signature",\n "cil",\n "opcode-shape",\n "call-shape",\n "field-shape",\n "exception-shape",\n "availability",\n "body-coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_method_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "properties": {\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "normalized_il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256",\n "normalized_il_sha256"\n ],\n "type": "object"\n },\n "status": {\n "enum": [\n "added",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "dimensions",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "metadata",\n "signature",\n "cil",\n "opcode-shape",\n "call-shape",\n "field-shape",\n "exception-shape",\n "availability",\n "body-coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mmc_method_[a-f0-9]{64}$",\n "type": "string"\n },\n "left": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "exact-il-signature",\n "exact-signature",\n "structural-method-shape",\n "field-signature"\n ],\n "type": "string"\n },\n "candidate_left_tokens": {\n "items": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "candidate_right_tokens": {\n "items": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "ambiguous",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_tokens",\n "candidate_right_tokens"\n ],\n "type": "object"\n },\n "right": {\n "type": "null"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "evidence_links",\n "limitations",\n "item_id",\n "dimensions",\n "status",\n "left",\n "right",\n "match"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "right": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "fields_total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "metadata_status": {\n "enum": [\n "absent",\n "complete",\n "partial",\n "malformed"\n ],\n "type": "string"\n },\n "methods_total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "artifact_sha256",\n "mvid",\n "module_name",\n "metadata_status",\n "methods_total",\n "fields_total"\n ],\n "type": "object"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "added": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "changed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unchanged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unchanged",\n "changed",\n "added",\n "removed",\n "unknown"\n ],\n "type": "object"\n }\n },\n "required": [\n "comparison_id",\n "algorithm",\n "left",\n "right",\n "summary",\n "matching",\n "methods",\n "fields",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Compare Managed Members"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Verify managed P/Invoke/native-boundary declarations against authenticated native export or function Evidence without executing managed code or translating managed metadata tokens into native addresses. The workflow preserves declaration-only, verified, inferred, contradicted, and unresolved states.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "managed_boundaries": {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_0b4af8f787323ba7fb52a35b1c21115b2c53d55d61ff9de4a16b006b818a2ba2",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "artifact": {\n "byte_length": 8192,\n "format": "pe",\n "path": "/examples/ManagedInterop.exe",\n "sha256": "6666666666666666666666666666666666666666666666666666666666666666"\n },\n "cli_native": {\n "il_only": true,\n "managed_native_header_rva": 0,\n "managed_native_header_size": 0,\n "native_entry_point": false,\n "ready_to_run_signature": false,\n "requires_32bit": false,\n "strong_name_signed": false\n },\n "coverage": {\n "issues": [],\n "state": "complete"\n },\n "identity_scope": {\n "requires_artifact_sha256": "6666666666666666666666666666666666666666666666666666666666666666",\n "requires_mvid": "00112233-4455-4677-8899-aabbccddeeff",\n "token_identity": "build-local"\n },\n "limitations": [],\n "metadata": {\n "status": "complete",\n "table_row_counts": {},\n "version": "v4.0.30319"\n },\n "module": {\n "enc_base_id": null,\n "enc_id": null,\n "generation": 0,\n "mvid": "00112233-4455-4677-8899-aabbccddeeff",\n "name": "ManagedInterop.exe",\n "row_offset": 0,\n "token": "0x00000001"\n },\n "module_refs": [\n {\n "name": "nativehelper.dll",\n "row_offset": 256,\n "token": "0x1a000001"\n }\n ],\n "native_implementations": [],\n "pinvoke_imports": [\n {\n "best_fit": "assembly-default",\n "call_convention": "cdecl",\n "char_set": "not-specified",\n "import_name": "open_native",\n "import_scope_name": "nativehelper.dll",\n "import_scope_token": "0x1a000001",\n "mapping_flags": 0,\n "mapping_flags_hex": "0x0000",\n "member_kind": "method",\n "member_name": "OpenNative",\n "member_token": "0x06000001",\n "no_mangle": true,\n "row_offset": 288,\n "supports_last_error": false,\n "throw_on_unmappable_char": "assembly-default",\n "token": "0x1c000001",\n "verification": "managed-declaration-only"\n }\n ],\n "summary": {\n "mixed_mode_or_native_header": false,\n "module_ref_count": 1,\n "native_implementation_count": 0,\n "pinvoke_import_count": 1,\n "ready_to_run": false\n }\n },\n "operation": "inspect_managed_native_boundaries",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n },\n "native_observations": [\n {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_9ec8ff855a81995540b10a08acf864e2003b588f92dd88cd2b796e3ad288fdc9",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "architectures": {\n "exhaustive": true,\n "items": [],\n "limitations": [],\n "total": 0\n },\n "build_metadata": {\n "exhaustive": true,\n "items": [],\n "limitations": [],\n "total": 0\n },\n "dependencies": {\n "exhaustive": true,\n "items": [],\n "limitations": [],\n "total": 0\n },\n "endian": "little",\n "entrypoints": {\n "exhaustive": true,\n "items": [],\n "limitations": [],\n "total": 0\n },\n "exports": {\n "exhaustive": true,\n "items": [\n {\n "address": "0x1000",\n "name": "open_native",\n "reexport": false,\n "source": "nm",\n "weak": false\n }\n ],\n "limitations": [],\n "total": 1\n },\n "file_type": "dylib",\n "flags": [],\n "format": "mach-o",\n "imports": {\n "exhaustive": true,\n "items": [],\n "limitations": [],\n "total": 0\n },\n "limitations": [],\n "load_commands": {\n "exhaustive": true,\n "items": [],\n "limitations": [],\n "total": 0\n },\n "provenance": [],\n "segments": {\n "exhaustive": true,\n "items": [],\n "limitations": [],\n "total": 0\n },\n "uuid": null,\n "word_size": 64\n },\n "operation": "inspect_macho",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "native-macos",\n "name": "REA native macOS provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": "x86_64",\n "digest": {\n "sha256": "7777777777777777777777777777777777777777777777777777777777777777"\n },\n "format": "mach-o",\n "local_path": "/examples/nativehelper.dll",\n "name": "nativehelper.dll"\n }\n }\n ]\n }\n ],\n "properties": {\n "managed_boundaries": {\n "additionalProperties": false,\n "description": "Value for managed boundaries.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "native_observations": {\n "description": "Native function or export Evidence records",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "managed_boundaries",\n "native_observations"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "verify_managed_native_boundaries",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "const": "rea-managed-native-verification",\n "type": "string"\n },\n "token_identity": {\n "const": "build-local",\n "type": "string"\n },\n "token_to_address_mapping": {\n "const": "not-inferred",\n "type": "string"\n }\n },\n "required": [\n "name",\n "token_identity",\n "token_to_address_mapping"\n ],\n "type": "object"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "status": {\n "enum": [\n "complete-within-inputs",\n "partial"\n ],\n "type": "string"\n }\n },\n "required": [\n "status"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "managed_boundary": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "minLength": 1,\n "type": "string"\n },\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage_state": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "metadata_status": {\n "enum": [\n "absent",\n "complete",\n "partial",\n "malformed"\n ],\n "type": "string"\n },\n "mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "native_implementations_total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "pinvoke_imports_total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "evidence_id",\n "artifact_sha256",\n "artifact_path",\n "mvid",\n "metadata_status",\n "pinvoke_imports_total",\n "native_implementations_total",\n "coverage_state"\n ],\n "type": "object"\n },\n "native_implementations": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "unresolved": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unresolved",\n "reason"\n ],\n "type": "object"\n },\n "native_observations": {\n "additionalProperties": false,\n "properties": {\n "accepted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symbols": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "unsupported": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "accepted",\n "unsupported",\n "symbols"\n ],\n "type": "object"\n },\n "pinvoke_imports": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "exact-export-name",\n "exact-function-name"\n ],\n "type": "string"\n },\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n },\n "prefixItems": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n }\n ],\n "type": "array"\n },\n "confidence": {\n "const": "observed",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mnv_pinvoke_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "managed": {\n "additionalProperties": false,\n "properties": {\n "call_convention": {\n "enum": [\n "not-specified",\n "winapi",\n "cdecl",\n "stdcall",\n "thiscall",\n "fastcall",\n "unknown"\n ],\n "type": "string"\n },\n "char_set": {\n "enum": [\n "not-specified",\n "ansi",\n "unicode",\n "auto",\n "unknown"\n ],\n "type": "string"\n },\n "declaration_verification": {\n "const": "managed-declaration-only",\n "type": "string"\n },\n "import_name": {\n "minLength": 1,\n "type": "string"\n },\n "import_scope_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "no_mangle": {\n "type": "boolean"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "member_token",\n "member_name",\n "import_name",\n "import_scope_name",\n "no_mangle",\n "char_set",\n "call_convention",\n "declaration_verification"\n ],\n "type": "object"\n },\n "matched_native": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n },\n "status": {\n "const": "verified",\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "managed",\n "evidence_links",\n "limitations",\n "status",\n "basis",\n "confidence",\n "matched_native",\n "candidates"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "decorated-name-candidate",\n "type": "string"\n },\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n },\n "prefixItems": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n }\n ],\n "type": "array"\n },\n "confidence": {\n "const": "inferred",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mnv_pinvoke_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "managed": {\n "additionalProperties": false,\n "properties": {\n "call_convention": {\n "enum": [\n "not-specified",\n "winapi",\n "cdecl",\n "stdcall",\n "thiscall",\n "fastcall",\n "unknown"\n ],\n "type": "string"\n },\n "char_set": {\n "enum": [\n "not-specified",\n "ansi",\n "unicode",\n "auto",\n "unknown"\n ],\n "type": "string"\n },\n "declaration_verification": {\n "const": "managed-declaration-only",\n "type": "string"\n },\n "import_name": {\n "minLength": 1,\n "type": "string"\n },\n "import_scope_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "no_mangle": {\n "type": "boolean"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "member_token",\n "member_name",\n "import_name",\n "import_scope_name",\n "no_mangle",\n "char_set",\n "call_convention",\n "declaration_verification"\n ],\n "type": "object"\n },\n "matched_native": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n },\n "status": {\n "const": "inferred",\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "managed",\n "evidence_links",\n "limitations",\n "status",\n "basis",\n "confidence",\n "matched_native",\n "candidates"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "no-native-candidate",\n "unsupported-native-evidence"\n ],\n "type": "string"\n },\n "candidates": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mnv_pinvoke_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "managed": {\n "additionalProperties": false,\n "properties": {\n "call_convention": {\n "enum": [\n "not-specified",\n "winapi",\n "cdecl",\n "stdcall",\n "thiscall",\n "fastcall",\n "unknown"\n ],\n "type": "string"\n },\n "char_set": {\n "enum": [\n "not-specified",\n "ansi",\n "unicode",\n "auto",\n "unknown"\n ],\n "type": "string"\n },\n "declaration_verification": {\n "const": "managed-declaration-only",\n "type": "string"\n },\n "import_name": {\n "minLength": 1,\n "type": "string"\n },\n "import_scope_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "no_mangle": {\n "type": "boolean"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "member_token",\n "member_name",\n "import_name",\n "import_scope_name",\n "no_mangle",\n "char_set",\n "call_convention",\n "declaration_verification"\n ],\n "type": "object"\n },\n "matched_native": {\n "type": "null"\n },\n "status": {\n "const": "unresolved",\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "managed",\n "evidence_links",\n "limitations",\n "status",\n "basis",\n "confidence",\n "matched_native",\n "candidates"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "module-mismatch",\n "type": "string"\n },\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n },\n "prefixItems": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n }\n ],\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "item_id": {\n "pattern": "^mnv_pinvoke_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "managed": {\n "additionalProperties": false,\n "properties": {\n "call_convention": {\n "enum": [\n "not-specified",\n "winapi",\n "cdecl",\n "stdcall",\n "thiscall",\n "fastcall",\n "unknown"\n ],\n "type": "string"\n },\n "char_set": {\n "enum": [\n "not-specified",\n "ansi",\n "unicode",\n "auto",\n "unknown"\n ],\n "type": "string"\n },\n "declaration_verification": {\n "const": "managed-declaration-only",\n "type": "string"\n },\n "import_name": {\n "minLength": 1,\n "type": "string"\n },\n "import_scope_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "member_token": {\n "anyOf": [\n {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "no_mangle": {\n "type": "boolean"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "member_token",\n "member_name",\n "import_name",\n "import_scope_name",\n "no_mangle",\n "char_set",\n "call_convention",\n "declaration_verification"\n ],\n "type": "object"\n },\n "matched_native": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "module_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "enum": [\n "macho-export",\n "function-dossier"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "name",\n "address",\n "module_name",\n "module_path",\n "source"\n ],\n "type": "object"\n },\n "status": {\n "const": "contradicted",\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "managed",\n "evidence_links",\n "limitations",\n "status",\n "basis",\n "confidence",\n "matched_native",\n "candidates"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "contradicted": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "inferred": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "native_body_unresolved": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unresolved": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "verified": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "verified",\n "inferred",\n "unresolved",\n "contradicted",\n "native_body_unresolved"\n ],\n "type": "object"\n },\n "verification_id": {\n "pattern": "^mnv_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "verification_id",\n "algorithm",\n "managed_boundary",\n "native_observations",\n "summary",\n "pinvoke_imports",\n "native_implementations",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Verify Managed Native Boundaries"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Import decompiler-produced managed reconstruction against authenticated inspect_managed_members Evidence. The workflow locks each method to artifact SHA-256, MVID, metadata token, signature hash, and the limited decoded-IL tuple hash, records the decompiler identity and options, and marks C# or pseudocode as analyst inference rather than canonical byte observation.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "decompiler": {\n "executable_sha256": null,\n "family": "ilspy",\n "name": "ilspycmd",\n "options": [\n "--disable-updatecheck",\n "--type",\n "Example.Program"\n ],\n "version": "9.1.0.7988"\n },\n "methods": [\n {\n "normalized_il_sha256": "5555555555555555555555555555555555555555555555555555555555555555",\n "reconstruction": {\n "end_line": 1,\n "kind": "decompiled-csharp",\n "language": "csharp",\n "source_path": "/examples/Example.Program.cs",\n "start_line": 1,\n "text": "internal static void Main() { }"\n },\n "signature_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "token": "0x06000001"\n }\n ],\n "notes": [\n "Synthetic example only; decompiler output is imported as inference."\n ],\n "static_members": {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_cf07661a99083e446ac5f18af3ca331cb1af7c3cd6cb7c9dc8bff880a1ed8bb9",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "artifact": {\n "byte_length": 4096,\n "format": "pe",\n "path": "/examples/22222222.dll",\n "sha256": "2222222222222222222222222222222222222222222222222222222222222222"\n },\n "call_edges": [],\n "coverage": {\n "issues": [],\n "state": "complete"\n },\n "field_accesses": [],\n "fields": [],\n "identity_scope": {\n "requires_artifact_sha256": "2222222222222222222222222222222222222222222222222222222222222222",\n "requires_mvid": "11112222-3333-4444-9555-666677778888",\n "token_identity": "build-local"\n },\n "limitations": [],\n "member_refs": [],\n "metadata": {\n "status": "complete",\n "table_row_counts": {},\n "version": "v4.0.30319"\n },\n "methods": [\n {\n "body": {\n "anchors": [],\n "decoded_instruction_count": 1,\n "exception_regions": [],\n "file_offset": 512,\n "header_format": "tiny",\n "il_sha256": "4444444444444444444444444444444444444444444444444444444444444444",\n "il_size": 1,\n "init_locals": false,\n "instruction_count": 1,\n "issue": null,\n "local_var_sig_token": null,\n "max_stack": 8,\n "normalized_il_sha256": "5555555555555555555555555555555555555555555555555555555555555555",\n "opcode_counts": {\n "ret": 1\n },\n "rva": 8192,\n "status": "present",\n "truncated_instructions": 0\n },\n "declaring_type": "Example.Program",\n "declaring_type_token": "0x02000001",\n "flags": 22,\n "impl_flags": 0,\n "name": "Main",\n "row_offset": 128,\n "rva": 8192,\n "signature": {\n "calling_convention": "default",\n "field_type": null,\n "generic_parameter_count": 0,\n "issue": null,\n "kind": "method",\n "parameter_count": 0,\n "parameter_types": [],\n "parse_status": "decoded",\n "raw_length": 3,\n "raw_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "return_type": "void"\n },\n "token": "0x06000001"\n }\n ],\n "module": {\n "enc_base_id": null,\n "enc_id": null,\n "generation": 0,\n "mvid": "11112222-3333-4444-9555-666677778888",\n "name": "Example.dll",\n "row_offset": 0,\n "token": "0x00000001"\n },\n "types": []\n },\n "operation": "inspect_managed_members",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n }\n }\n ],\n "properties": {\n "decompiler": {\n "additionalProperties": false,\n "description": "Value for decompiler.",\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Exact SHA-256 digest of executable."\n },\n "family": {\n "description": "Value for family.",\n "enum": [\n "ilspy",\n "dnspy",\n "dnlib",\n "mono-cecil",\n "other"\n ],\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "options": {\n "default": [],\n "description": "Value for options.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "version": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for version."\n }\n },\n "required": [\n "name",\n "family"\n ],\n "type": "object"\n },\n "methods": {\n "description": "Value for methods.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "normalized_il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact SHA-256 digest of normalized il."\n },\n "reconstruction": {\n "additionalProperties": false,\n "description": "Value for reconstruction.",\n "properties": {\n "end_line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for end line."\n },\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "decompiled-csharp",\n "decompiled-il",\n "semantic-pseudocode"\n ],\n "type": "string"\n },\n "language": {\n "description": "Value for language.",\n "enum": [\n "csharp",\n "il",\n "pseudocode"\n ],\n "type": "string"\n },\n "source_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Local filesystem path for source."\n },\n "start_line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for start line."\n },\n "text": {\n "description": "Value for text.",\n "minLength": 1,\n "type": "string"\n },\n "text_sha256": {\n "description": "Exact SHA-256 digest of text.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "language",\n "text"\n ],\n "type": "object"\n },\n "signature_sha256": {\n "description": "Exact SHA-256 digest of signature.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "description": "Value for token.",\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n }\n },\n "required": [\n "token",\n "signature_sha256",\n "normalized_il_sha256",\n "reconstruction"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "notes": {\n "default": [],\n "description": "Value for notes.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "static_members": {\n "additionalProperties": false,\n "description": "Value for static members.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "static_members",\n "decompiler",\n "methods"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "import_managed_reconstruction",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "decompiler": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "family": {\n "enum": [\n "ilspy",\n "dnspy",\n "dnlib",\n "mono-cecil",\n "other"\n ],\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "options": {\n "default": [],\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "options_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n }\n },\n "required": [\n "name",\n "version",\n "family",\n "executable_sha256",\n "options",\n "options_sha256"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 1,\n "minItems": 1,\n "type": "array"\n },\n "executed": {\n "const": false,\n "type": "boolean"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "methods": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "body_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "declaring_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "il_size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "name": {\n "type": "string"\n },\n "normalized_il_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "reconstruction": {\n "additionalProperties": false,\n "properties": {\n "end_line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "decompiled-csharp",\n "decompiled-il",\n "semantic-pseudocode"\n ],\n "type": "string"\n },\n "language": {\n "enum": [\n "csharp",\n "il",\n "pseudocode"\n ],\n "type": "string"\n },\n "line_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "start_line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "text": {\n "minLength": 1,\n "type": "string"\n },\n "text_length": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "text_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "language",\n "text",\n "text_sha256",\n "text_length",\n "line_count",\n "source_path",\n "start_line",\n "end_line"\n ],\n "type": "object"\n },\n "signature_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "token": {\n "pattern": "^0x[0-9a-f]{8}$",\n "type": "string"\n },\n "validation": {\n "additionalProperties": false,\n "properties": {\n "canonical_observation": {\n "const": false,\n "type": "boolean"\n },\n "confidence_floor": {\n "const": "inference",\n "type": "string"\n },\n "exact_build_required": {\n "const": true,\n "type": "boolean"\n },\n "matched_static_member": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "matched_static_member",\n "exact_build_required",\n "canonical_observation",\n "confidence_floor"\n ],\n "type": "object"\n }\n },\n "required": [\n "token",\n "declaring_type",\n "name",\n "signature_sha256",\n "normalized_il_sha256",\n "body_sha256",\n "il_size",\n "reconstruction",\n "validation"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "notes": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "phase": {\n "const": "reconstruction-import",\n "type": "string"\n },\n "reconstruction_id": {\n "pattern": "^mre_[a-f0-9]{64}$",\n "type": "string"\n },\n "static_observation": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "minLength": 1,\n "type": "string"\n },\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "byte_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "metadata_status": {\n "enum": [\n "absent",\n "complete",\n "partial",\n "malformed"\n ],\n "type": "string"\n },\n "module_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "mvid": {\n "anyOf": [\n {\n "pattern": "^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "artifact_sha256",\n "artifact_path",\n "byte_length",\n "module_name",\n "mvid",\n "metadata_status"\n ],\n "type": "object"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "decompiled_csharp_methods": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "decompiled_il_methods": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "imported_methods": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "pseudocode_methods": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total_text_bytes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "imported_methods",\n "decompiled_csharp_methods",\n "decompiled_il_methods",\n "pseudocode_methods",\n "total_text_bytes"\n ],\n "type": "object"\n }\n },\n "required": [\n "reconstruction_id",\n "phase",\n "executed",\n "static_observation",\n "decompiler",\n "summary",\n "methods",\n "notes",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Import Managed Reconstruction"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Project authenticated managed artifact, member, and native-boundary Evidence into the provider-neutral application graph without executing managed code, loading assemblies, or translating managed metadata tokens to native addresses. The graph preserves managed static observations as managed-specific nodes linked to source Evidence.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "managed_artifact": {\n "additionalProperties": false,\n "description": "Value for managed artifact.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "managed_members": {\n "additionalProperties": false,\n "description": "Value for managed members.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "managed_native_boundaries": {\n "additionalProperties": false,\n "description": "Value for managed native boundaries.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "managed_artifact"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "managed_artifact": {\n "additionalProperties": false,\n "description": "Value for managed artifact.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "managed_members": {\n "additionalProperties": false,\n "description": "Value for managed members.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "managed_native_boundaries": {\n "additionalProperties": false,\n "description": "Value for managed native boundaries.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "managed_members"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "managed_artifact": {\n "additionalProperties": false,\n "description": "Value for managed artifact.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "managed_members": {\n "additionalProperties": false,\n "description": "Value for managed members.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "managed_native_boundaries": {\n "additionalProperties": false,\n "description": "Value for managed native boundaries.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "managed_native_boundaries"\n ],\n "type": "object"\n }\n ],\n "examples": [\n {\n "managed_members": {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_cf07661a99083e446ac5f18af3ca331cb1af7c3cd6cb7c9dc8bff880a1ed8bb9",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "artifact": {\n "byte_length": 4096,\n "format": "pe",\n "path": "/examples/22222222.dll",\n "sha256": "2222222222222222222222222222222222222222222222222222222222222222"\n },\n "call_edges": [],\n "coverage": {\n "issues": [],\n "state": "complete"\n },\n "field_accesses": [],\n "fields": [],\n "identity_scope": {\n "requires_artifact_sha256": "2222222222222222222222222222222222222222222222222222222222222222",\n "requires_mvid": "11112222-3333-4444-9555-666677778888",\n "token_identity": "build-local"\n },\n "limitations": [],\n "member_refs": [],\n "metadata": {\n "status": "complete",\n "table_row_counts": {},\n "version": "v4.0.30319"\n },\n "methods": [\n {\n "body": {\n "anchors": [],\n "decoded_instruction_count": 1,\n "exception_regions": [],\n "file_offset": 512,\n "header_format": "tiny",\n "il_sha256": "4444444444444444444444444444444444444444444444444444444444444444",\n "il_size": 1,\n "init_locals": false,\n "instruction_count": 1,\n "issue": null,\n "local_var_sig_token": null,\n "max_stack": 8,\n "normalized_il_sha256": "5555555555555555555555555555555555555555555555555555555555555555",\n "opcode_counts": {\n "ret": 1\n },\n "rva": 8192,\n "status": "present",\n "truncated_instructions": 0\n },\n "declaring_type": "Example.Program",\n "declaring_type_token": "0x02000001",\n "flags": 22,\n "impl_flags": 0,\n "name": "Main",\n "row_offset": 128,\n "rva": 8192,\n "signature": {\n "calling_convention": "default",\n "field_type": null,\n "generic_parameter_count": 0,\n "issue": null,\n "kind": "method",\n "parameter_count": 0,\n "parameter_types": [],\n "parse_status": "decoded",\n "raw_length": 3,\n "raw_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "return_type": "void"\n },\n "token": "0x06000001"\n }\n ],\n "module": {\n "enc_base_id": null,\n "enc_id": null,\n "generation": 0,\n "mvid": "11112222-3333-4444-9555-666677778888",\n "name": "Example.dll",\n "row_offset": 0,\n "token": "0x00000001"\n },\n "types": []\n },\n "operation": "inspect_managed_members",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-dotnet-static",\n "name": "REA managed static analysis provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n }\n }\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "project_managed_application_graph",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "status": {\n "enum": [\n "complete-within-inputs",\n "partial"\n ],\n "type": "string"\n }\n },\n "required": [\n "status"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "graph": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "edge_id": {\n "pattern": "^jag_edge_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "relationship-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n },\n "relation": {\n "enum": [\n "contains",\n "loads",\n "imports",\n "maps_to",\n "exposes",\n "sends",\n "invokes",\n "handles",\n "calls",\n "persists_to",\n "observed_as",\n "changed_from"\n ],\n "type": "string"\n },\n "source_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "target_node_id",\n "relation",\n "properties",\n "evidence",\n "edge_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "identity": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "global-exact",\n "type": "string"\n },\n "strategy": {\n "const": "content-digest",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "original_source": {\n "type": "string"\n },\n "source_map_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "stability": {\n "const": "source-map-exact",\n "type": "string"\n },\n "strategy": {\n "const": "source-map-original",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "source_map_sha256",\n "original_source",\n "source_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "canonical-path",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "minLength": 1,\n "type": "string"\n },\n "basis": {\n "items": {\n "enum": [\n "syntax-tree",\n "control-flow",\n "imports",\n "exports",\n "literals",\n "api-usage"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "fingerprint_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "cross-version-inference",\n "type": "string"\n },\n "strategy": {\n "const": "structural-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "algorithm",\n "fingerprint_sha256",\n "basis"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "key": {\n "type": "string"\n },\n "namespace": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "artifact-local-key",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "namespace",\n "key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_key": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "capture-only",\n "type": "string"\n },\n "strategy": {\n "const": "runtime-instance",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "capture_sha256",\n "runtime_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observation_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "observation-only",\n "type": "string"\n },\n "strategy": {\n "const": "observation-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "observation_sha256",\n "scope"\n ],\n "type": "object"\n }\n ]\n },\n "kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "observations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "observation-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "label": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "observation_id": {\n "pattern": "^jag_observation_[a-f0-9]{64}$",\n "type": "string"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "label",\n "properties",\n "evidence",\n "observation_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "kind",\n "identity",\n "observations"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "root_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "schema": {\n "const": "JavaScriptApplicationGraph",\n "type": "string"\n }\n },\n "required": [\n "schema",\n "root_node_ids",\n "nodes",\n "edges",\n "coverage",\n "limitations",\n "graph_id"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "projection_id": {\n "pattern": "^magp_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_evidence": {\n "additionalProperties": false,\n "properties": {\n "managed_artifact_evidence_id": {\n "anyOf": [\n {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "managed_members_evidence_id": {\n "anyOf": [\n {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "managed_native_boundaries_evidence_id": {\n "anyOf": [\n {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "managed_artifact_evidence_id",\n "managed_members_evidence_id",\n "managed_native_boundaries_evidence_id"\n ],\n "type": "object"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "assemblies": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "fields": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "graph_edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "graph_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "methods": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "modules": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "native_implementations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "pinvoke_imports": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "types": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "graph_nodes",\n "graph_edges",\n "assemblies",\n "modules",\n "types",\n "methods",\n "fields",\n "pinvoke_imports",\n "native_implementations"\n ],\n "type": "object"\n }\n },\n "required": [\n "projection_id",\n "root_artifact_sha256",\n "source_evidence",\n "summary",\n "graph",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Project Managed Application Graph"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "List page targets exposed by the selected loopback Chrome DevTools Protocol endpoint. Optionally filter by exact origin. URLs preserve query values and fragments; only URL userinfo credentials are removed.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222"\n }\n ],\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "list_browser_targets",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "excluded": {\n "additionalProperties": false,\n "properties": {\n "disallowed_origin": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "non_page": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unsupported_url": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "disallowed_origin",\n "unsupported_url",\n "non_page"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "targets": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "origin": {\n "type": "string"\n },\n "target_id": {\n "type": "string"\n },\n "title": {\n "type": "string"\n },\n "type": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "title",\n "url",\n "origin",\n "attached"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "browser",\n "targets",\n "excluded",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Browser Targets"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Passively inspect one selected page target through CDP without evaluating JavaScript, navigating, clicking, closing, or mutating the page. If no origin filter is supplied, capture is scoped to the target\'s current origin. Returns DOM structure, accessibility, scripts, resources, attach-window network and console metadata, workers, and redacted storage inventory as Evidence.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "include_accessibility_text": {\n "default": false,\n "description": "Whether to include accessibility text in the result.",\n "type": "boolean"\n },\n "include_console_text": {\n "default": false,\n "description": "Whether to include console text in the result.",\n "type": "boolean"\n },\n "include_json_body_shapes": {\n "default": false,\n "description": "Whether to include json body shapes in the result.",\n "type": "boolean"\n },\n "include_script_sources": {\n "const": false,\n "default": false,\n "description": "Whether to include script sources in the result.",\n "type": "boolean"\n },\n "include_storage_fingerprints": {\n "default": false,\n "description": "Whether to include storage fingerprints in the result.",\n "type": "boolean"\n },\n "include_storage_keys": {\n "default": false,\n "description": "Whether to include storage keys in the result.",\n "type": "boolean"\n },\n "include_websocket_shapes": {\n "default": false,\n "description": "Whether to include websocket shapes in the result.",\n "type": "boolean"\n },\n "observation_ms": {\n "default": 500,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "include_accessibility_text": {\n "default": false,\n "description": "Whether to include accessibility text in the result.",\n "type": "boolean"\n },\n "include_console_text": {\n "default": false,\n "description": "Whether to include console text in the result.",\n "type": "boolean"\n },\n "include_json_body_shapes": {\n "default": false,\n "description": "Whether to include json body shapes in the result.",\n "type": "boolean"\n },\n "include_script_sources": {\n "const": true,\n "default": true,\n "description": "Whether to include script sources in the result.",\n "type": "boolean"\n },\n "include_storage_fingerprints": {\n "default": false,\n "description": "Whether to include storage fingerprints in the result.",\n "type": "boolean"\n },\n "include_storage_keys": {\n "default": false,\n "description": "Whether to include storage keys in the result.",\n "type": "boolean"\n },\n "include_websocket_shapes": {\n "default": false,\n "description": "Whether to include websocket shapes in the result.",\n "type": "boolean"\n },\n "observation_ms": {\n "default": 500,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n }\n ],\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222",\n "target_id": "TARGET_ID_FROM_LIST_BROWSER_TARGETS"\n }\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "inspect_web_page",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "accessibility": {\n "additionalProperties": false,\n "properties": {\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "description": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "ignored": {\n "type": "boolean"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "node_id": {\n "type": "string"\n },\n "parent_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "states": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "number"\n },\n {\n "type": "string"\n }\n ]\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "parent_id",\n "role",\n "name",\n "description",\n "ignored",\n "states"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "text_capture": {\n "additionalProperties": false,\n "properties": {\n "excluded_fields": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "retained_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "not_approved",\n "included",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "retained_bytes",\n "excluded_fields"\n ],\n "type": "object"\n },\n "total_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_nodes",\n "text_capture",\n "nodes"\n ],\n "type": "object"\n },\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "capture_window": {\n "additionalProperties": false,\n "properties": {\n "ended_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "observation_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "started_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n }\n },\n "required": [\n "started_at",\n "ended_at",\n "observation_ms"\n ],\n "type": "object"\n },\n "completeness": {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "console": {\n "additionalProperties": false,\n "properties": {\n "coverage_started_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "events": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "argument_types": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "text_capture": {\n "additionalProperties": false,\n "properties": {\n "retained_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "not_approved",\n "included"\n ],\n "type": "string"\n },\n "values": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "argument_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "argument_index",\n "type",\n "text"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "values",\n "retained_bytes"\n ],\n "type": "object"\n },\n "timestamp": {\n "type": "number"\n },\n "type": {\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "type",\n "timestamp",\n "argument_types",\n "url",\n "line",\n "column",\n "text_capture"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "prior_activity_available": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "events",\n "coverage_started_at",\n "prior_activity_available"\n ],\n "type": "object"\n },\n "dom": {\n "additionalProperties": false,\n "properties": {\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attribute_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_name": {\n "type": "string"\n },\n "node_type": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_value_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "parent_index": {\n "maximum": 9007199254740991,\n "minimum": -1,\n "type": "integer"\n }\n },\n "required": [\n "index",\n "parent_index",\n "node_type",\n "node_name",\n "node_value_length",\n "attribute_names"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_nodes",\n "nodes"\n ],\n "type": "object"\n },\n "frames": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "frame_id": {\n "type": "string"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "frame_id",\n "parent_frame_id",\n "url",\n "origin"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "metadata": {\n "additionalProperties": false,\n "properties": {\n "agent_hints": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "declaration": {\n "type": "string"\n },\n "mechanism": {\n "enum": [\n "link_rel",\n "dom_link_rel",\n "well_known_resource",\n "response_header"\n ],\n "type": "string"\n },\n "trust": {\n "const": "page-declared-untrusted",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "mechanism",\n "declaration",\n "url",\n "trust"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "dom_urls": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attribute": {\n "enum": [\n "href",\n "src",\n "action",\n "formaction",\n "poster"\n ],\n "type": "string"\n },\n "destination_scope": {\n "enum": [\n "approved",\n "outside_policy",\n "unsupported"\n ],\n "type": "string"\n },\n "node_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "node_index",\n "attribute",\n "url",\n "destination_scope"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "excluded_dom_urls": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "headers_allowlisted": {\n "const": true,\n "type": "boolean"\n },\n "responses": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "content_encoding": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "content_length": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "csp": {\n "additionalProperties": false,\n "properties": {\n "directives": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "keyword",\n "scheme",\n "approved_origin",\n "external_origin",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "name",\n "sources"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "hash_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nonce_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "directives",\n "nonce_count",\n "hash_count"\n ],\n "type": "object"\n },\n "links": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "as": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "crossorigin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "destination_scope": {\n "enum": [\n "approved",\n "outside_policy",\n "unsupported"\n ],\n "type": "string"\n },\n "href": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "rel": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "href",\n "destination_scope",\n "rel",\n "as",\n "type",\n "crossorigin"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "mime_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "policies": {\n "additionalProperties": false,\n "properties": {\n "coep": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "coop": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "corp": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "permissions_policy_features": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "referrer_policy": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "x_content_type_options": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "coop",\n "coep",\n "corp",\n "referrer_policy",\n "x_content_type_options",\n "permissions_policy_features"\n ],\n "type": "object"\n },\n "request_id": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "url",\n "mime_type",\n "content_length",\n "content_encoding",\n "csp",\n "links",\n "policies"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "responses",\n "dom_urls",\n "agent_hints",\n "excluded_dom_urls",\n "headers_allowlisted"\n ],\n "type": "object"\n },\n "network": {\n "additionalProperties": false,\n "properties": {\n "coverage_started_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "prior_activity_available": {\n "const": false,\n "type": "boolean"\n },\n "requests": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "body_shapes": {\n "additionalProperties": false,\n "properties": {\n "request": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "max_depth_observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "observations": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "types": {\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "response": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "max_depth_observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "observations": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "types": {\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "not_approved",\n "included",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "request",\n "response"\n ],\n "type": "object"\n },\n "encoded_data_length": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "initiator": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "type": {\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "type",\n "url",\n "line",\n "column"\n ],\n "type": "object"\n },\n "method": {\n "type": "string"\n },\n "mime_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "request_id": {\n "type": "string"\n },\n "resource_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "anyOf": [\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "url",\n "origin",\n "method",\n "resource_type",\n "status",\n "mime_type",\n "encoded_data_length",\n "initiator",\n "body_shapes"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "websocket_events": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "direction": {\n "enum": [\n "sent",\n "received"\n ],\n "type": "string"\n },\n "opcode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_shape": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "format": {\n "enum": [\n "json",\n "text",\n "binary"\n ],\n "type": "string"\n },\n "json_shape": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "max_depth_observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "observations": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "types": {\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "format",\n "json_shape"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "request_id": {\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "direction",\n "opcode",\n "payload_bytes",\n "payload_shape"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "requests",\n "websocket_events",\n "coverage_started_at",\n "prior_activity_available"\n ],\n "type": "object"\n },\n "resources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "content_size": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "mime_type": {\n "type": "string"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_key": {\n "pattern": "^res_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "resource_key",\n "url",\n "origin",\n "type",\n "mime_type",\n "content_size"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "scripts": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "cdp_hash": {\n "type": "string"\n },\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "is_module": {\n "type": "boolean"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_reconciliation": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "resource_key": {\n "type": "string"\n },\n "status": {\n "const": "exact",\n "type": "string"\n }\n },\n "required": [\n "status",\n "resource_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "candidate_resource_keys": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "ambiguous",\n "type": "string"\n }\n },\n "required": [\n "status",\n "candidate_resource_keys"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "const": "no_exact_sanitized_url",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "script_key": {\n "pattern": "^scr_[a-f0-9]{64}(?:_[0-9]+)?$",\n "type": "string"\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "included": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "included",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "type": "string"\n },\n "media_type": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n "included": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "included",\n "artifact"\n ],\n "type": "object"\n }\n ]\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "frame_id",\n "url",\n "origin",\n "cdp_hash",\n "length",\n "is_module",\n "language",\n "source_map_url",\n "resource_reconciliation",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "items"\n ],\n "type": "object"\n },\n "storage": {\n "additionalProperties": false,\n "properties": {\n "cache_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "content_fingerprints": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "complete": {\n "type": "boolean"\n },\n "identity_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "enum": [\n "cookie",\n "local_storage",\n "session_storage",\n "indexed_db_schema",\n "indexed_db_record",\n "cache_entry"\n ],\n "type": "string"\n },\n "value_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "scope",\n "identity_sha256",\n "value_sha256",\n "complete"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "fingerprint_algorithm": {\n "const": "sha256",\n "default": "sha256",\n "type": "string"\n },\n "fingerprints_complete": {\n "default": false,\n "type": "boolean"\n },\n "indexed_db_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "local_storage_keys": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "origin": {\n "type": "string"\n },\n "quota_bytes": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "session_storage_keys": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "usage_bytes": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "values_redacted": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "origin",\n "usage_bytes",\n "quota_bytes",\n "local_storage_keys",\n "session_storage_keys",\n "indexed_db_names",\n "cache_names",\n "content_fingerprints",\n "fingerprint_algorithm",\n "fingerprints_complete",\n "values_redacted"\n ],\n "type": "object"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "origin": {\n "type": "string"\n },\n "target_id": {\n "type": "string"\n },\n "title": {\n "type": "string"\n },\n "type": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "title",\n "url",\n "origin",\n "attached"\n ],\n "type": "object"\n },\n "workers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "opener_target_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "target_id": {\n "type": "string"\n },\n "type": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "url",\n "origin",\n "attached",\n "opener_target_id",\n "parent_frame_id"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "browser",\n "target",\n "capture_window",\n "completeness",\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "resources",\n "network",\n "console",\n "workers",\n "metadata",\n "storage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Web Page"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Capture JavaScript source from one selected CDP page and statically derive a chunk graph, route and endpoint candidates, vendor fingerprints, page-declared WebMCP metadata, and optionally fetch source maps from the selected target origin or explicitly requested origins. JavaScript is parsed but never executed.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222",\n "fetch_source_maps": false,\n "include_accessibility_text": false,\n "include_script_sources": true,\n "include_storage_keys": false,\n "observation_ms": 500,\n "target_id": "TARGET_ID_FROM_LIST_BROWSER_TARGETS"\n }\n ],\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "fetch_source_maps": {\n "default": false,\n "description": "Value for fetch source maps.",\n "type": "boolean"\n },\n "include_accessibility_text": {\n "default": false,\n "description": "Whether to include accessibility text in the result.",\n "type": "boolean"\n },\n "include_console_text": {\n "default": false,\n "description": "Whether to include console text in the result.",\n "type": "boolean"\n },\n "include_json_body_shapes": {\n "default": false,\n "description": "Whether to include json body shapes in the result.",\n "type": "boolean"\n },\n "include_script_sources": {\n "const": true,\n "default": true,\n "description": "Whether to include script sources in the result.",\n "type": "boolean"\n },\n "include_storage_fingerprints": {\n "default": false,\n "description": "Whether to include storage fingerprints in the result.",\n "type": "boolean"\n },\n "include_storage_keys": {\n "default": false,\n "description": "Whether to include storage keys in the result.",\n "type": "boolean"\n },\n "include_websocket_shapes": {\n "default": false,\n "description": "Whether to include websocket shapes in the result.",\n "type": "boolean"\n },\n "observation_ms": {\n "default": 500,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "analyze_web_bundle",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "capture": {\n "additionalProperties": false,\n "properties": {\n "scripts_analyzed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts_observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "source_artifacts": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "type": "string"\n },\n "media_type": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text_available": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text_available"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "target_url": {\n "type": "string"\n }\n },\n "required": [\n "target_url",\n "scripts_observed",\n "scripts_analyzed",\n "source_artifacts"\n ],\n "type": "object"\n },\n "completeness": {\n "additionalProperties": false,\n "properties": {\n "parse_failures": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "parsed_scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n },\n "visited_ast_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "parsed_scripts",\n "parse_failures",\n "visited_ast_nodes"\n ],\n "type": "object"\n },\n "inferences": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "detector": {\n "type": "string"\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "artifact_sha256",\n "line",\n "column",\n "detector"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "confidence": {\n "enum": [\n "low",\n "medium",\n "high"\n ],\n "type": "string"\n },\n "kind": {\n "enum": [\n "vendor_fingerprint",\n "route_framework",\n "bundle_runtime"\n ],\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value",\n "confidence",\n "basis"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "observations": {\n "additionalProperties": false,\n "properties": {\n "chunks": {\n "additionalProperties": false,\n "properties": {\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "from_script_key": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "static_import",\n "dynamic_import",\n "require",\n "worker_import"\n ],\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "line",\n "column"\n ],\n "type": "object"\n },\n "resolved_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "specifier": {\n "type": "string"\n }\n },\n "required": [\n "from_script_key",\n "kind",\n "specifier",\n "resolved_url",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "script_key": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "url",\n "artifact_sha256",\n "bytes"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "nodes",\n "edges"\n ],\n "type": "object"\n },\n "endpoints": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "location": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "line",\n "column"\n ],\n "type": "object"\n },\n "mechanism": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "value",\n "mechanism",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "routes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "location": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "line",\n "column"\n ],\n "type": "object"\n },\n "mechanism": {\n "type": "string"\n },\n "value": {\n "type": "string"\n }\n },\n "required": [\n "value",\n "mechanism",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "source_maps": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "type": "string"\n },\n "media_type": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n "declared_url": {\n "type": "string"\n },\n "limitation": {\n "type": "null"\n },\n "mappings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "generated_column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "generated_line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "original_column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "original_line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source": {\n "type": "string"\n }\n },\n "required": [\n "generated_line",\n "generated_column",\n "source",\n "original_line",\n "original_column",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "original_module_edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "from_source": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "static_import",\n "dynamic_import",\n "require"\n ],\n "type": "string"\n },\n "resolved_source": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "specifier": {\n "type": "string"\n }\n },\n "required": [\n "from_source",\n "kind",\n "specifier",\n "resolved_source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "original_sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "type": "string"\n },\n "media_type": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "source": {\n "type": "string"\n }\n },\n "required": [\n "source",\n "artifact"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "script_key": {\n "type": "string"\n },\n "status": {\n "const": "included",\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "declared_url",\n "artifact",\n "original_sources",\n "original_module_edges",\n "mappings",\n "status",\n "limitation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "type": "string"\n },\n "media_type": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n "declared_url": {\n "type": "string"\n },\n "limitation": {\n "type": "string"\n },\n "mappings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "generated_column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "generated_line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "original_column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "original_line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source": {\n "type": "string"\n }\n },\n "required": [\n "generated_line",\n "generated_column",\n "source",\n "original_line",\n "original_column",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "original_module_edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "from_source": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "static_import",\n "dynamic_import",\n "require"\n ],\n "type": "string"\n },\n "resolved_source": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "specifier": {\n "type": "string"\n }\n },\n "required": [\n "from_source",\n "kind",\n "specifier",\n "resolved_source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "original_sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "type": "string"\n },\n "media_type": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "source": {\n "type": "string"\n }\n },\n "required": [\n "source",\n "artifact"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "script_key": {\n "type": "string"\n },\n "status": {\n "const": "partial",\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "declared_url",\n "artifact",\n "original_sources",\n "original_module_edges",\n "mappings",\n "status",\n "limitation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "type": "null"\n },\n "declared_url": {\n "type": "string"\n },\n "limitation": {\n "type": "string"\n },\n "mappings": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "original_module_edges": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "original_sources": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "script_key": {\n "type": "string"\n },\n "status": {\n "enum": [\n "fetch_failed",\n "invalid",\n "policy_filtered"\n ],\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "declared_url",\n "artifact",\n "original_sources",\n "original_module_edges",\n "mappings",\n "status",\n "limitation"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "processed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "requested": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "not_requested",\n "included",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "requested",\n "processed",\n "items"\n ],\n "type": "object"\n },\n "webmcp_declarations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "description": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "line",\n "column"\n ],\n "type": "object"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "schema_property_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "trust": {\n "const": "page-declared-untrusted",\n "type": "string"\n }\n },\n "required": [\n "name",\n "description",\n "schema_property_names",\n "trust",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "chunks",\n "routes",\n "endpoints",\n "webmcp_declarations",\n "source_maps"\n ],\n "type": "object"\n },\n "unknowns": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "affected_script_keys": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "dimension": {\n "type": "string"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "dimension",\n "reason",\n "affected_script_keys"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "capture",\n "observations",\n "inferences",\n "unknowns",\n "completeness",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Analyze Web Bundle"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Arm a CDP observation window of the requested duration while the user operates the page. By default it follows the selected target\'s current origin; an explicit origin list controls which origins may be retained. Records navigation, redirect, lifecycle, and failure events.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222",\n "observation_ms": 10000,\n "target_id": "TARGET_ID_FROM_LIST_BROWSER_TARGETS"\n }\n ],\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "observation_ms": {\n "default": 10000,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "observe_web_session",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "completeness": {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "final_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "initial_url": {\n "type": "string"\n },\n "target_id": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "initial_url",\n "final_url"\n ],\n "type": "object"\n },\n "timeline": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "destination_scope": {\n "anyOf": [\n {\n "enum": [\n "approved",\n "outside_policy",\n "unsupported"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "detail": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "loader_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "request_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "timestamp": {\n "minimum": 0,\n "type": "number"\n },\n "type": {\n "enum": [\n "navigation_requested",\n "navigation_committed",\n "same_origin_reload",\n "same_document_navigation",\n "redirect",\n "load_failed",\n "lifecycle",\n "target_terminated"\n ],\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "sequence",\n "type",\n "timestamp",\n "frame_id",\n "loader_id",\n "request_id",\n "url",\n "destination_scope",\n "detail"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "armed_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "end_reason": {\n "enum": [\n "window_elapsed",\n "target_left_scope",\n "target_terminated"\n ],\n "type": "string"\n },\n "ended_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "requested_ms": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "armed_at",\n "ended_at",\n "requested_ms",\n "end_reason"\n ],\n "type": "object"\n }\n },\n "required": [\n "browser",\n "target",\n "window",\n "timeline",\n "completeness",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Observe Web Session"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Passively inventory every page-registered WebMCP tool and its complete structural input-schema summary using the experimental CDP WebMCP domain. Metadata is page-declared-untrusted; REA never registers or invokes discovered tools.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222",\n "observation_ms": 100,\n "target_id": "TARGET_ID_FROM_LIST_BROWSER_TARGETS"\n }\n ],\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "observation_ms": {\n "default": 100,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "discover_webmcp_tools",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "completeness": {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "available",\n "unavailable"\n ],\n "type": "string"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "type": "string"\n },\n "target_id": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "url",\n "origin"\n ],\n "type": "object"\n },\n "tools": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "annotations": {\n "additionalProperties": false,\n "properties": {\n "autosubmit": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "read_only": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "untrusted_content": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "read_only",\n "untrusted_content",\n "autosubmit"\n ],\n "type": "object"\n },\n "declaration_kind": {\n "enum": [\n "declarative",\n "imperative"\n ],\n "type": "string"\n },\n "description": {\n "type": "string"\n },\n "frame_id": {\n "type": "string"\n },\n "frame_url": {\n "type": "string"\n },\n "input_schema_shape": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "max_depth_observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "observations": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "types": {\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "owner_origin": {\n "type": "string"\n },\n "registration_source": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "line",\n "column"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "tool_key": {\n "pattern": "^webmcp_[a-f0-9]{64}$",\n "type": "string"\n },\n "trust": {\n "const": "page-declared-untrusted",\n "type": "string"\n }\n },\n "required": [\n "tool_key",\n "name",\n "description",\n "frame_id",\n "frame_url",\n "owner_origin",\n "declaration_kind",\n "input_schema_shape",\n "annotations",\n "registration_source",\n "trust"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "items"\n ],\n "type": "object"\n }\n },\n "required": [\n "browser",\n "target",\n "status",\n "tools",\n "completeness",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Discover Webmcp Tools"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare passive web captures by providing before and after, or compare recorded scenarios by providing before_scenario and after_scenario with an optional normalization policy. Use exactly one input group. Scenario comparison aligns exact step IDs, records deterministic literal normalization, and returns every artifact-level difference plus alignment failures inline. Missing or truncated evidence never proves equality.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "after_scenario": {\n "browser": {\n "cleanup": "disconnected-external",\n "mode": "connect",\n "process_ownership": "external",\n "product": "Chromium",\n "version": "149"\n },\n "completeness": {\n "equality_eligible": true,\n "missing_sections": [],\n "status": "complete",\n "truncated_sections": []\n },\n "duration_ms": 10,\n "events": {\n "dropped": 0,\n "items": [],\n "retained": 0\n },\n "limitations": [],\n "scenario": {\n "action_count": 1,\n "secret_references": [],\n "start_origin": "https://app.example.test"\n },\n "steps": [\n {\n "action": "scenario_start",\n "after_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "artifacts": {\n "accessibility": {\n "state": "not_requested"\n },\n "dom": {\n "state": "not_requested"\n },\n "history": {\n "state": "not_requested"\n },\n "screenshot": {\n "state": "not_requested"\n },\n "storage": {\n "state": "not_requested"\n },\n "url": {\n "state": "not_requested"\n }\n },\n "before_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "completeness": {\n "equality_eligible": true,\n "missing_sections": [],\n "status": "complete",\n "truncated_sections": []\n },\n "elapsed_ms": 0,\n "error": null,\n "event_sequence_end": 0,\n "event_sequence_start": 1,\n "status": "completed",\n "step_id": "scenario_start",\n "step_index": 0\n },\n {\n "action": "click",\n "after_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "artifacts": {\n "accessibility": {\n "state": "not_requested"\n },\n "dom": {\n "state": "not_requested"\n },\n "history": {\n "state": "not_requested"\n },\n "screenshot": {\n "state": "not_requested"\n },\n "storage": {\n "state": "not_requested"\n },\n "url": {\n "state": "not_requested"\n }\n },\n "before_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "completeness": {\n "equality_eligible": true,\n "missing_sections": [],\n "status": "complete",\n "truncated_sections": []\n },\n "elapsed_ms": 10,\n "error": null,\n "event_sequence_end": 0,\n "event_sequence_start": 1,\n "status": "completed",\n "step_id": "open-settings",\n "step_index": 1\n }\n ]\n },\n "before_scenario": {\n "browser": {\n "cleanup": "disconnected-external",\n "mode": "connect",\n "process_ownership": "external",\n "product": "Chromium",\n "version": "149"\n },\n "completeness": {\n "equality_eligible": true,\n "missing_sections": [],\n "status": "complete",\n "truncated_sections": []\n },\n "duration_ms": 10,\n "events": {\n "dropped": 0,\n "items": [],\n "retained": 0\n },\n "limitations": [],\n "scenario": {\n "action_count": 1,\n "secret_references": [],\n "start_origin": "https://app.example.test"\n },\n "steps": [\n {\n "action": "scenario_start",\n "after_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "artifacts": {\n "accessibility": {\n "state": "not_requested"\n },\n "dom": {\n "state": "not_requested"\n },\n "history": {\n "state": "not_requested"\n },\n "screenshot": {\n "state": "not_requested"\n },\n "storage": {\n "state": "not_requested"\n },\n "url": {\n "state": "not_requested"\n }\n },\n "before_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "completeness": {\n "equality_eligible": true,\n "missing_sections": [],\n "status": "complete",\n "truncated_sections": []\n },\n "elapsed_ms": 0,\n "error": null,\n "event_sequence_end": 0,\n "event_sequence_start": 1,\n "status": "completed",\n "step_id": "scenario_start",\n "step_index": 0\n },\n {\n "action": "click",\n "after_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "artifacts": {\n "accessibility": {\n "state": "not_requested"\n },\n "dom": {\n "state": "not_requested"\n },\n "history": {\n "state": "not_requested"\n },\n "screenshot": {\n "state": "not_requested"\n },\n "storage": {\n "state": "not_requested"\n },\n "url": {\n "state": "not_requested"\n }\n },\n "before_url": {\n "origin": "https://app.example.test",\n "query_parameter_names": [],\n "redacted": false,\n "url": "https://app.example.test/"\n },\n "completeness": {\n "equality_eligible": true,\n "missing_sections": [],\n "status": "complete",\n "truncated_sections": []\n },\n "elapsed_ms": 10,\n "error": null,\n "event_sequence_end": 0,\n "event_sequence_start": 1,\n "status": "completed",\n "step_id": "open-settings",\n "step_index": 1\n }\n ]\n },\n "normalization": {\n "rules": [\n {\n "artifacts": [\n "dom",\n "accessibility"\n ],\n "match": "build-123",\n "replacement": "[BUILD_ID]",\n "rule_id": "volatile-build-id"\n }\n ]\n }\n }\n ],\n "properties": {\n "after": {\n "description": "Later passive web-page capture.",\n "properties": {\n "inspection": {\n "description": "Value for inspection.",\n "properties": {\n "accessibility": {\n "description": "Value for accessibility.",\n "properties": {\n "nodes": {\n "description": "Value for nodes.",\n "items": {\n "properties": {\n "description": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for description."\n },\n "ignored": {\n "description": "Value for ignored.",\n "type": "boolean"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact name used by this operation."\n },\n "node_id": {\n "description": "Value for node id.",\n "type": "string"\n },\n "parent_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for parent id."\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for role."\n },\n "states": {\n "default": [],\n "description": "Value for states.",\n "items": {\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "number"\n },\n {\n "type": "string"\n }\n ],\n "description": "Value for value."\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "parent_id",\n "role",\n "name",\n "description",\n "ignored"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "text_capture": {\n "description": "Value for text capture.",\n "properties": {\n "excluded_fields": {\n "description": "Value for excluded fields.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "retained_bytes": {\n "description": "Byte count for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "not_approved",\n "included",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "retained_bytes",\n "excluded_fields"\n ],\n "type": "object"\n },\n "total_nodes": {\n "description": "Value for total nodes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_nodes",\n "text_capture",\n "nodes"\n ],\n "type": "object"\n },\n "browser": {\n "description": "Value for browser.",\n "properties": {\n "js_version": {\n "description": "Value for js version.",\n "type": "string"\n },\n "product": {\n "description": "Value for product.",\n "type": "string"\n },\n "protocol_version": {\n "description": "Value for protocol version.",\n "type": "string"\n },\n "revision": {\n "description": "Value for revision.",\n "type": "string"\n },\n "user_agent": {\n "description": "Value for user agent.",\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "capture_window": {\n "description": "Value for capture window.",\n "properties": {\n "ended_at": {\n "description": "Value for ended at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "observation_ms": {\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "started_at": {\n "description": "Value for started at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n }\n },\n "required": [\n "started_at",\n "ended_at",\n "observation_ms"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "properties": {\n "attach_limited_sections": {\n "description": "Value for attach limited sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "description": "Value for conditions.",\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "description": "Value for dropped events.",\n "properties": {\n "console_events": {\n "description": "Value for console events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "description": "Value for network requests.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "description": "Value for scripts.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "description": "Value for timeline events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "description": "Value for webmcp tools.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "description": "Value for websocket connections.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "description": "Value for websocket frames.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "description": "Value for excluded.",\n "items": {\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for count."\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "description": "Value for section.",\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "description": "Value for policy filtered sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "description": "Value for unavailable sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "console": {\n "description": "Value for console.",\n "properties": {\n "coverage_started_at": {\n "description": "Value for coverage started at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "events": {\n "description": "Value for events.",\n "items": {\n "properties": {\n "argument_types": {\n "description": "Value for argument types.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for column."\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for line."\n },\n "text_capture": {\n "description": "Value for text capture.",\n "properties": {\n "retained_bytes": {\n "description": "Byte count for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "not_approved",\n "included"\n ],\n "type": "string"\n },\n "values": {\n "description": "Value for values.",\n "items": {\n "properties": {\n "argument_index": {\n "description": "Value for argument index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n }\n },\n "required": [\n "argument_index",\n "type",\n "text"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "values",\n "retained_bytes"\n ],\n "type": "object"\n },\n "timestamp": {\n "description": "Value for timestamp.",\n "type": "number"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "type",\n "timestamp",\n "argument_types",\n "url",\n "line",\n "column",\n "text_capture"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "prior_activity_available": {\n "const": false,\n "description": "Value for prior activity available.",\n "type": "boolean"\n }\n },\n "required": [\n "events",\n "coverage_started_at",\n "prior_activity_available"\n ],\n "type": "object"\n },\n "dom": {\n "description": "Value for dom.",\n "properties": {\n "nodes": {\n "description": "Value for nodes.",\n "items": {\n "properties": {\n "attribute_names": {\n "description": "Value for attribute names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "index": {\n "description": "Value for index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_name": {\n "description": "Value for node name.",\n "type": "string"\n },\n "node_type": {\n "description": "Value for node type.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_value_length": {\n "description": "Value for node value length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "parent_index": {\n "description": "Value for parent index.",\n "maximum": 9007199254740991,\n "minimum": -1,\n "type": "integer"\n }\n },\n "required": [\n "index",\n "parent_index",\n "node_type",\n "node_name",\n "node_value_length",\n "attribute_names"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total_nodes": {\n "description": "Value for total nodes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_nodes",\n "nodes"\n ],\n "type": "object"\n },\n "frames": {\n "description": "Value for frames.",\n "items": {\n "properties": {\n "frame_id": {\n "description": "Value for frame id.",\n "type": "string"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for parent frame id."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "frame_id",\n "parent_frame_id",\n "url",\n "origin"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "metadata": {\n "description": "Value for metadata.",\n "properties": {\n "agent_hints": {\n "description": "Value for agent hints.",\n "items": {\n "properties": {\n "declaration": {\n "description": "Value for declaration.",\n "type": "string"\n },\n "mechanism": {\n "description": "Value for mechanism.",\n "enum": [\n "link_rel",\n "dom_link_rel",\n "well_known_resource",\n "response_header"\n ],\n "type": "string"\n },\n "trust": {\n "const": "page-declared-untrusted",\n "description": "Value for trust.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "mechanism",\n "declaration",\n "url",\n "trust"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "dom_urls": {\n "description": "Value for dom urls.",\n "items": {\n "properties": {\n "attribute": {\n "description": "Value for attribute.",\n "enum": [\n "href",\n "src",\n "action",\n "formaction",\n "poster"\n ],\n "type": "string"\n },\n "destination_scope": {\n "description": "Value for destination scope.",\n "enum": [\n "approved",\n "outside_policy",\n "unsupported"\n ],\n "type": "string"\n },\n "node_index": {\n "description": "Value for node index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "node_index",\n "attribute",\n "url",\n "destination_scope"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "excluded_dom_urls": {\n "description": "Value for excluded dom urls.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "headers_allowlisted": {\n "const": true,\n "description": "Value for headers allowlisted.",\n "type": "boolean"\n },\n "responses": {\n "description": "Value for responses.",\n "items": {\n "properties": {\n "content_encoding": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for content encoding."\n },\n "content_length": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for content length."\n },\n "csp": {\n "description": "Value for csp.",\n "properties": {\n "directives": {\n "description": "Value for directives.",\n "items": {\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "sources": {\n "description": "Value for sources.",\n "items": {\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "keyword",\n "scheme",\n "approved_origin",\n "external_origin",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for value."\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "name",\n "sources"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "hash_count": {\n "description": "Value for hash count.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nonce_count": {\n "description": "Value for nonce count.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "directives",\n "nonce_count",\n "hash_count"\n ],\n "type": "object"\n },\n "links": {\n "description": "Value for links.",\n "items": {\n "properties": {\n "as": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for as."\n },\n "crossorigin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for crossorigin."\n },\n "destination_scope": {\n "description": "Value for destination scope.",\n "enum": [\n "approved",\n "outside_policy",\n "unsupported"\n ],\n "type": "string"\n },\n "href": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for href."\n },\n "rel": {\n "description": "Value for rel.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for type."\n }\n },\n "required": [\n "href",\n "destination_scope",\n "rel",\n "as",\n "type",\n "crossorigin"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "mime_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for mime type."\n },\n "policies": {\n "description": "Value for policies.",\n "properties": {\n "coep": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for coep."\n },\n "coop": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for coop."\n },\n "corp": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for corp."\n },\n "permissions_policy_features": {\n "description": "Value for permissions policy features.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "referrer_policy": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for referrer policy."\n },\n "x_content_type_options": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for x content type options."\n }\n },\n "required": [\n "coop",\n "coep",\n "corp",\n "referrer_policy",\n "x_content_type_options",\n "permissions_policy_features"\n ],\n "type": "object"\n },\n "request_id": {\n "description": "Value for request id.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "url",\n "mime_type",\n "content_length",\n "content_encoding",\n "csp",\n "links",\n "policies"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "responses",\n "dom_urls",\n "agent_hints",\n "excluded_dom_urls",\n "headers_allowlisted"\n ],\n "type": "object"\n },\n "network": {\n "description": "Value for network.",\n "properties": {\n "coverage_started_at": {\n "description": "Value for coverage started at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "prior_activity_available": {\n "const": false,\n "description": "Value for prior activity available.",\n "type": "boolean"\n },\n "requests": {\n "description": "Value for requests.",\n "items": {\n "properties": {\n "body_shapes": {\n "description": "Value for body shapes.",\n "properties": {\n "request": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for request."\n },\n "response": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for response."\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "not_approved",\n "included",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "request",\n "response"\n ],\n "type": "object"\n },\n "encoded_data_length": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for encoded data length."\n },\n "initiator": {\n "description": "Value for initiator.",\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for column."\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for line."\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "type",\n "url",\n "line",\n "column"\n ],\n "type": "object"\n },\n "method": {\n "description": "Value for method.",\n "type": "string"\n },\n "mime_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for mime type."\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "request_id": {\n "description": "Value for request id.",\n "type": "string"\n },\n "resource_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for resource type."\n },\n "status": {\n "anyOf": [\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Current lifecycle or verification status."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "url",\n "origin",\n "method",\n "resource_type",\n "status",\n "mime_type",\n "encoded_data_length",\n "initiator",\n "body_shapes"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "websocket_events": {\n "description": "Value for websocket events.",\n "items": {\n "properties": {\n "direction": {\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "sent",\n "received"\n ],\n "type": "string"\n },\n "opcode": {\n "description": "Value for opcode.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_bytes": {\n "description": "Byte count for payload.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_shape": {\n "anyOf": [\n {\n "properties": {\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "json",\n "text",\n "binary"\n ],\n "type": "string"\n },\n "json_shape": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for json shape."\n }\n },\n "required": [\n "format",\n "json_shape"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for payload shape."\n },\n "request_id": {\n "description": "Value for request id.",\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "direction",\n "opcode",\n "payload_bytes",\n "payload_shape"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "requests",\n "websocket_events",\n "coverage_started_at",\n "prior_activity_available"\n ],\n "type": "object"\n },\n "resources": {\n "description": "Value for resources.",\n "items": {\n "properties": {\n "content_size": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for content size."\n },\n "mime_type": {\n "description": "Value for mime type.",\n "type": "string"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "resource_key": {\n "description": "Value for resource key.",\n "pattern": "^res_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "resource_key",\n "url",\n "origin",\n "type",\n "mime_type",\n "content_size"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "scripts": {\n "description": "Value for scripts.",\n "properties": {\n "items": {\n "description": "Value for items.",\n "items": {\n "properties": {\n "cdp_hash": {\n "description": "Value for cdp hash.",\n "type": "string"\n },\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for frame id."\n },\n "is_module": {\n "description": "Whether is module.",\n "type": "boolean"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for language."\n },\n "length": {\n "description": "Value for length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "resource_reconciliation": {\n "description": "Value for resource reconciliation.",\n "oneOf": [\n {\n "properties": {\n "resource_key": {\n "description": "Value for resource key.",\n "type": "string"\n },\n "status": {\n "const": "exact",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n }\n },\n "required": [\n "status",\n "resource_key"\n ],\n "type": "object"\n },\n {\n "properties": {\n "candidate_resource_keys": {\n "description": "Value for candidate resource keys.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "ambiguous",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n }\n },\n "required": [\n "status",\n "candidate_resource_keys"\n ],\n "type": "object"\n },\n {\n "properties": {\n "reason": {\n "const": "no_exact_sanitized_url",\n "description": "Value for reason.",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "script_key": {\n "description": "Value for script key.",\n "pattern": "^scr_[a-f0-9]{64}(?:_[0-9]+)?$",\n "type": "string"\n },\n "source": {\n "description": "Value for source.",\n "oneOf": [\n {\n "properties": {\n "included": {\n "const": false,\n "description": "Value for included.",\n "type": "boolean"\n },\n "reason": {\n "description": "Value for reason.",\n "type": "string"\n }\n },\n "required": [\n "included",\n "reason"\n ],\n "type": "object"\n },\n {\n "properties": {\n "artifact": {\n "description": "Value for artifact.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "description": "Value for charset.",\n "type": "string"\n },\n "media_type": {\n "description": "Value for media type.",\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n "included": {\n "const": true,\n "description": "Value for included.",\n "type": "boolean"\n }\n },\n "required": [\n "included",\n "artifact"\n ],\n "type": "object"\n }\n ]\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for source map url."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "url",\n "origin",\n "cdp_hash",\n "length",\n "is_module",\n "language",\n "source_map_url",\n "resource_reconciliation",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "items"\n ],\n "type": "object"\n },\n "storage": {\n "description": "Value for storage.",\n "properties": {\n "cache_names": {\n "description": "Value for cache names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "content_fingerprints": {\n "default": [],\n "description": "Value for content fingerprints.",\n "items": {\n "properties": {\n "complete": {\n "description": "Value for complete.",\n "type": "boolean"\n },\n "identity_sha256": {\n "description": "Exact SHA-256 digest of identity.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "description": "Value for scope.",\n "enum": [\n "cookie",\n "local_storage",\n "session_storage",\n "indexed_db_schema",\n "indexed_db_record",\n "cache_entry"\n ],\n "type": "string"\n },\n "value_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact SHA-256 digest of value."\n }\n },\n "required": [\n "scope",\n "identity_sha256",\n "value_sha256",\n "complete"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "fingerprint_algorithm": {\n "const": "sha256",\n "default": "sha256",\n "description": "Value for fingerprint algorithm.",\n "type": "string"\n },\n "fingerprints_complete": {\n "default": false,\n "description": "Value for fingerprints complete.",\n "type": "boolean"\n },\n "indexed_db_names": {\n "description": "Value for indexed db names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "local_storage_keys": {\n "description": "Value for local storage keys.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "origin": {\n "description": "Value for origin.",\n "type": "string"\n },\n "quota_bytes": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Byte count for quota."\n },\n "session_storage_keys": {\n "description": "Value for session storage keys.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "usage_bytes": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Byte count for usage."\n },\n "values_redacted": {\n "const": true,\n "description": "Value for values redacted.",\n "type": "boolean"\n }\n },\n "required": [\n "origin",\n "usage_bytes",\n "quota_bytes",\n "local_storage_keys",\n "session_storage_keys",\n "indexed_db_names",\n "cache_names",\n "values_redacted"\n ],\n "type": "object"\n },\n "target": {\n "description": "Value for target.",\n "properties": {\n "attached": {\n "description": "Value for attached.",\n "type": "boolean"\n },\n "origin": {\n "description": "Value for origin.",\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "type": "string"\n },\n "title": {\n "description": "Value for title.",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "title",\n "url",\n "origin",\n "attached"\n ],\n "type": "object"\n },\n "workers": {\n "description": "Value for workers.",\n "items": {\n "properties": {\n "attached": {\n "description": "Value for attached.",\n "type": "boolean"\n },\n "opener_target_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for opener target id."\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for parent frame id."\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "url",\n "origin",\n "attached"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "browser",\n "target",\n "capture_window",\n "completeness",\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "resources",\n "network",\n "console",\n "workers",\n "metadata",\n "storage",\n "limitations"\n ],\n "type": "object"\n },\n "webmcp": {\n "anyOf": [\n {\n "properties": {\n "browser": {\n "description": "Value for browser.",\n "properties": {\n "js_version": {\n "description": "Value for js version.",\n "type": "string"\n },\n "product": {\n "description": "Value for product.",\n "type": "string"\n },\n "protocol_version": {\n "description": "Value for protocol version.",\n "type": "string"\n },\n "revision": {\n "description": "Value for revision.",\n "type": "string"\n },\n "user_agent": {\n "description": "Value for user agent.",\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "properties": {\n "attach_limited_sections": {\n "description": "Value for attach limited sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "description": "Value for conditions.",\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "description": "Value for dropped events.",\n "properties": {\n "console_events": {\n "description": "Value for console events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "description": "Value for network requests.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "description": "Value for scripts.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "description": "Value for timeline events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "description": "Value for webmcp tools.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "description": "Value for websocket connections.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "description": "Value for websocket frames.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "description": "Value for excluded.",\n "items": {\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for count."\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "description": "Value for section.",\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "description": "Value for policy filtered sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "description": "Value for unavailable sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "available",\n "unavailable"\n ],\n "type": "string"\n },\n "target": {\n "description": "Value for target.",\n "properties": {\n "origin": {\n "description": "Value for origin.",\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "url",\n "origin"\n ],\n "type": "object"\n },\n "tools": {\n "description": "Value for tools.",\n "properties": {\n "items": {\n "description": "Value for items.",\n "items": {\n "properties": {\n "annotations": {\n "description": "Value for annotations.",\n "properties": {\n "autosubmit": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for autosubmit."\n },\n "read_only": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for read only."\n },\n "untrusted_content": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for untrusted content."\n }\n },\n "required": [\n "read_only",\n "untrusted_content",\n "autosubmit"\n ],\n "type": "object"\n },\n "declaration_kind": {\n "description": "Value for declaration kind.",\n "enum": [\n "declarative",\n "imperative"\n ],\n "type": "string"\n },\n "description": {\n "description": "Value for description.",\n "type": "string"\n },\n "frame_id": {\n "description": "Value for frame id.",\n "type": "string"\n },\n "frame_url": {\n "description": "Value for frame url.",\n "type": "string"\n },\n "input_schema_shape": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for input schema shape."\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "owner_origin": {\n "description": "Value for owner origin.",\n "type": "string"\n },\n "registration_source": {\n "anyOf": [\n {\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for column."\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for line."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "line",\n "column"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for registration source."\n },\n "tool_key": {\n "description": "Value for tool key.",\n "pattern": "^webmcp_[a-f0-9]{64}$",\n "type": "string"\n },\n "trust": {\n "const": "page-declared-untrusted",\n "description": "Value for trust.",\n "type": "string"\n }\n },\n "required": [\n "tool_key",\n "name",\n "description",\n "frame_id",\n "frame_url",\n "owner_origin",\n "declaration_kind",\n "input_schema_shape",\n "annotations",\n "registration_source",\n "trust"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "items"\n ],\n "type": "object"\n }\n },\n "required": [\n "browser",\n "target",\n "status",\n "tools",\n "completeness",\n "limitations"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for webmcp."\n }\n },\n "required": [\n "inspection"\n ],\n "type": "object"\n },\n "after_scenario": {\n "additionalProperties": false,\n "description": "Later browser scenario capture.",\n "properties": {\n "browser": {\n "additionalProperties": false,\n "description": "Value for browser.",\n "properties": {\n "cleanup": {\n "description": "Value for cleanup.",\n "enum": [\n "terminated-owned-process",\n "disconnected-external"\n ],\n "type": "string"\n },\n "mode": {\n "description": "Operation mode that selects the requested behavior.",\n "enum": [\n "launch",\n "connect"\n ],\n "type": "string"\n },\n "process_ownership": {\n "description": "Value for process ownership.",\n "enum": [\n "provider-owned",\n "external"\n ],\n "type": "string"\n },\n "product": {\n "description": "Value for product.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "mode",\n "process_ownership",\n "cleanup",\n "product",\n "version"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "duration_ms": {\n "description": "Value for duration ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "events": {\n "additionalProperties": false,\n "description": "Value for events.",\n "properties": {\n "dropped": {\n "description": "Value for dropped.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "items": {\n "description": "Value for items.",\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "console",\n "description": "Value for kind.",\n "type": "string"\n },\n "level": {\n "description": "Value for level.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "level",\n "text",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "page-error",\n "description": "Value for kind.",\n "type": "string"\n },\n "message": {\n "description": "Value for message.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "stack": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for stack."\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "message",\n "stack"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "response",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "maximum": 599,\n "minimum": 100,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "minLength": 1,\n "type": "string"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-failed",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-finished",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-unfinished",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "capture-ended",\n "cancelled"\n ],\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "body": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_exposed",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "description": "Value for message.",\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "request-failed",\n "response-unfinished",\n "body-read-failed",\n "body-read-timeout",\n "cancelled",\n "capture-ended"\n ],\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "message"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "content": {\n "description": "Value for content.",\n "type": "string"\n },\n "encoding": {\n "const": "base64",\n "description": "Value for encoding.",\n "type": "string"\n },\n "media_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for media type."\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "representation": {\n "description": "Value for representation.",\n "enum": [\n "browser-exposed-request-bytes",\n "browser-decoded-response-bytes"\n ],\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "representation",\n "encoding",\n "content",\n "bytes",\n "sha256",\n "media_type",\n "redacted"\n ],\n "type": "object"\n }\n ],\n "description": "Value for body."\n },\n "headers": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "description": "Value for message.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "message"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "items": {\n "description": "Value for items.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for value."\n }\n },\n "required": [\n "name",\n "value",\n "redacted"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "items"\n ],\n "type": "object"\n }\n ],\n "description": "Value for headers."\n },\n "kind": {\n "const": "network-content",\n "description": "Value for kind.",\n "type": "string"\n },\n "phase": {\n "description": "Current plan or execution phase of the operation.",\n "enum": [\n "request",\n "response"\n ],\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source_event_sequence": {\n "description": "Value for source event sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "transaction_id",\n "source_event_sequence",\n "phase",\n "headers",\n "body"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "websocket-opened",\n "websocket-closed"\n ],\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "websocket-frame-sent",\n "websocket-frame-received"\n ],\n "type": "string"\n },\n "payload_bytes": {\n "description": "Byte count for payload.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_text": {\n "description": "Value for payload text.",\n "type": "string"\n },\n "payload_type": {\n "const": "text",\n "description": "Value for payload type.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "description": "Value for truncated.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "payload_bytes",\n "truncated",\n "payload_type",\n "payload_text"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "websocket-frame-sent",\n "websocket-frame-received"\n ],\n "type": "string"\n },\n "payload_bytes": {\n "description": "Byte count for payload.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_text": {\n "description": "Value for payload text.",\n "type": "null"\n },\n "payload_type": {\n "const": "binary",\n "description": "Value for payload type.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "description": "Value for truncated.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "payload_bytes",\n "truncated",\n "payload_type",\n "payload_text"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "frame-attached",\n "frame-detached",\n "frame-navigated",\n "worker-created",\n "worker-closed",\n "popup-opened",\n "popup-closed"\n ],\n "type": "string"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact name used by this operation."\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "anyOf": [\n {\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "download-cancelled",\n "description": "Value for kind.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "suggested_filename": {\n "description": "Value for suggested filename.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "suggested_filename",\n "url"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "retained",\n "dropped",\n "items"\n ],\n "type": "object"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "scenario": {\n "additionalProperties": false,\n "description": "Value for scenario.",\n "properties": {\n "action_count": {\n "description": "Value for action count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "network_content": {\n "additionalProperties": false,\n "description": "Value for network content.",\n "properties": {\n "header_values": {\n "default": false,\n "description": "Value for header values.",\n "type": "boolean"\n },\n "request_body": {\n "default": false,\n "description": "Value for request body.",\n "type": "boolean"\n },\n "response_body": {\n "default": false,\n "description": "Value for response body.",\n "type": "boolean"\n }\n },\n "type": "object"\n },\n "secret_references": {\n "description": "Value for secret references.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "start_origin": {\n "description": "Value for start origin.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "start_origin",\n "action_count",\n "secret_references"\n ],\n "type": "object"\n },\n "steps": {\n "description": "Value for steps.",\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "description": "Value for action.",\n "minLength": 1,\n "type": "string"\n },\n "after_url": {\n "description": "Value for after url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "artifacts": {\n "additionalProperties": false,\n "description": "Value for artifacts.",\n "properties": {\n "accessibility": {\n "description": "Value for accessibility.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "dom": {\n "description": "Value for dom.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "history": {\n "description": "Value for history.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "current_url": {\n "description": "Value for current url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "length": {\n "description": "Value for length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "navigation_entries": {\n "description": "Value for navigation entries.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "type": {\n "description": "Value for type.",\n "enum": [\n "navigate",\n "reload",\n "back_forward",\n "prerender",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "type",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "length",\n "current_url",\n "navigation_entries"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "screenshot": {\n "description": "Value for screenshot.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "description": "Value for data base64.",\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "description": "Value for media type.",\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "storage": {\n "description": "Value for storage.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "cookies": {\n "description": "Value for cookies.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "local_storage": {\n "description": "Value for local storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "session_storage": {\n "description": "Value for session storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "cookies",\n "local_storage",\n "session_storage"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "description": "Value for url.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "object"\n },\n "before_url": {\n "description": "Value for before url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "elapsed_ms": {\n "description": "Value for elapsed ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "description": "Structured, caller-actionable error when the operation fails.",\n "type": "null"\n },\n "event_sequence_end": {\n "description": "Value for event sequence end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "event_sequence_start": {\n "description": "Value for event sequence start.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "const": "completed",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_index",\n "step_id",\n "action",\n "elapsed_ms",\n "before_url",\n "after_url",\n "event_sequence_start",\n "event_sequence_end",\n "artifacts",\n "completeness",\n "status",\n "error"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "description": "Value for action.",\n "minLength": 1,\n "type": "string"\n },\n "after_url": {\n "description": "Value for after url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "artifacts": {\n "additionalProperties": false,\n "description": "Value for artifacts.",\n "properties": {\n "accessibility": {\n "description": "Value for accessibility.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "dom": {\n "description": "Value for dom.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "history": {\n "description": "Value for history.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "current_url": {\n "description": "Value for current url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "length": {\n "description": "Value for length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "navigation_entries": {\n "description": "Value for navigation entries.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "type": {\n "description": "Value for type.",\n "enum": [\n "navigate",\n "reload",\n "back_forward",\n "prerender",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "type",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "length",\n "current_url",\n "navigation_entries"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "screenshot": {\n "description": "Value for screenshot.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "description": "Value for data base64.",\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "description": "Value for media type.",\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "storage": {\n "description": "Value for storage.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "cookies": {\n "description": "Value for cookies.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "local_storage": {\n "description": "Value for local storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "session_storage": {\n "description": "Value for session storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "cookies",\n "local_storage",\n "session_storage"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "description": "Value for url.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "object"\n },\n "before_url": {\n "description": "Value for before url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "elapsed_ms": {\n "description": "Value for elapsed ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "description": "Structured, caller-actionable error when the operation fails.",\n "type": "string"\n },\n "event_sequence_end": {\n "description": "Value for event sequence end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "event_sequence_start": {\n "description": "Value for event sequence start.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "failed",\n "cancelled"\n ],\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_index",\n "step_id",\n "action",\n "elapsed_ms",\n "before_url",\n "after_url",\n "event_sequence_start",\n "event_sequence_end",\n "artifacts",\n "completeness",\n "status",\n "error"\n ],\n "type": "object"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "browser",\n "scenario",\n "duration_ms",\n "steps",\n "events",\n "completeness",\n "limitations"\n ],\n "type": "object"\n },\n "before": {\n "description": "Earlier passive web-page capture.",\n "properties": {\n "inspection": {\n "description": "Value for inspection.",\n "properties": {\n "accessibility": {\n "description": "Value for accessibility.",\n "properties": {\n "nodes": {\n "description": "Value for nodes.",\n "items": {\n "properties": {\n "description": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for description."\n },\n "ignored": {\n "description": "Value for ignored.",\n "type": "boolean"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact name used by this operation."\n },\n "node_id": {\n "description": "Value for node id.",\n "type": "string"\n },\n "parent_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for parent id."\n },\n "role": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for role."\n },\n "states": {\n "default": [],\n "description": "Value for states.",\n "items": {\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "number"\n },\n {\n "type": "string"\n }\n ],\n "description": "Value for value."\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "parent_id",\n "role",\n "name",\n "description",\n "ignored"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "text_capture": {\n "description": "Value for text capture.",\n "properties": {\n "excluded_fields": {\n "description": "Value for excluded fields.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "retained_bytes": {\n "description": "Byte count for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "not_approved",\n "included",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "retained_bytes",\n "excluded_fields"\n ],\n "type": "object"\n },\n "total_nodes": {\n "description": "Value for total nodes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_nodes",\n "text_capture",\n "nodes"\n ],\n "type": "object"\n },\n "browser": {\n "description": "Value for browser.",\n "properties": {\n "js_version": {\n "description": "Value for js version.",\n "type": "string"\n },\n "product": {\n "description": "Value for product.",\n "type": "string"\n },\n "protocol_version": {\n "description": "Value for protocol version.",\n "type": "string"\n },\n "revision": {\n "description": "Value for revision.",\n "type": "string"\n },\n "user_agent": {\n "description": "Value for user agent.",\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "capture_window": {\n "description": "Value for capture window.",\n "properties": {\n "ended_at": {\n "description": "Value for ended at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "observation_ms": {\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "started_at": {\n "description": "Value for started at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n }\n },\n "required": [\n "started_at",\n "ended_at",\n "observation_ms"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "properties": {\n "attach_limited_sections": {\n "description": "Value for attach limited sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "description": "Value for conditions.",\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "description": "Value for dropped events.",\n "properties": {\n "console_events": {\n "description": "Value for console events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "description": "Value for network requests.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "description": "Value for scripts.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "description": "Value for timeline events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "description": "Value for webmcp tools.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "description": "Value for websocket connections.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "description": "Value for websocket frames.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "description": "Value for excluded.",\n "items": {\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for count."\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "description": "Value for section.",\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "description": "Value for policy filtered sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "description": "Value for unavailable sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "console": {\n "description": "Value for console.",\n "properties": {\n "coverage_started_at": {\n "description": "Value for coverage started at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "events": {\n "description": "Value for events.",\n "items": {\n "properties": {\n "argument_types": {\n "description": "Value for argument types.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for column."\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for line."\n },\n "text_capture": {\n "description": "Value for text capture.",\n "properties": {\n "retained_bytes": {\n "description": "Byte count for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "not_approved",\n "included"\n ],\n "type": "string"\n },\n "values": {\n "description": "Value for values.",\n "items": {\n "properties": {\n "argument_index": {\n "description": "Value for argument index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n }\n },\n "required": [\n "argument_index",\n "type",\n "text"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "values",\n "retained_bytes"\n ],\n "type": "object"\n },\n "timestamp": {\n "description": "Value for timestamp.",\n "type": "number"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "type",\n "timestamp",\n "argument_types",\n "url",\n "line",\n "column",\n "text_capture"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "prior_activity_available": {\n "const": false,\n "description": "Value for prior activity available.",\n "type": "boolean"\n }\n },\n "required": [\n "events",\n "coverage_started_at",\n "prior_activity_available"\n ],\n "type": "object"\n },\n "dom": {\n "description": "Value for dom.",\n "properties": {\n "nodes": {\n "description": "Value for nodes.",\n "items": {\n "properties": {\n "attribute_names": {\n "description": "Value for attribute names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "index": {\n "description": "Value for index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_name": {\n "description": "Value for node name.",\n "type": "string"\n },\n "node_type": {\n "description": "Value for node type.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_value_length": {\n "description": "Value for node value length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "parent_index": {\n "description": "Value for parent index.",\n "maximum": 9007199254740991,\n "minimum": -1,\n "type": "integer"\n }\n },\n "required": [\n "index",\n "parent_index",\n "node_type",\n "node_name",\n "node_value_length",\n "attribute_names"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total_nodes": {\n "description": "Value for total nodes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_nodes",\n "nodes"\n ],\n "type": "object"\n },\n "frames": {\n "description": "Value for frames.",\n "items": {\n "properties": {\n "frame_id": {\n "description": "Value for frame id.",\n "type": "string"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for parent frame id."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "frame_id",\n "parent_frame_id",\n "url",\n "origin"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "metadata": {\n "description": "Value for metadata.",\n "properties": {\n "agent_hints": {\n "description": "Value for agent hints.",\n "items": {\n "properties": {\n "declaration": {\n "description": "Value for declaration.",\n "type": "string"\n },\n "mechanism": {\n "description": "Value for mechanism.",\n "enum": [\n "link_rel",\n "dom_link_rel",\n "well_known_resource",\n "response_header"\n ],\n "type": "string"\n },\n "trust": {\n "const": "page-declared-untrusted",\n "description": "Value for trust.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "mechanism",\n "declaration",\n "url",\n "trust"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "dom_urls": {\n "description": "Value for dom urls.",\n "items": {\n "properties": {\n "attribute": {\n "description": "Value for attribute.",\n "enum": [\n "href",\n "src",\n "action",\n "formaction",\n "poster"\n ],\n "type": "string"\n },\n "destination_scope": {\n "description": "Value for destination scope.",\n "enum": [\n "approved",\n "outside_policy",\n "unsupported"\n ],\n "type": "string"\n },\n "node_index": {\n "description": "Value for node index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "node_index",\n "attribute",\n "url",\n "destination_scope"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "excluded_dom_urls": {\n "description": "Value for excluded dom urls.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "headers_allowlisted": {\n "const": true,\n "description": "Value for headers allowlisted.",\n "type": "boolean"\n },\n "responses": {\n "description": "Value for responses.",\n "items": {\n "properties": {\n "content_encoding": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for content encoding."\n },\n "content_length": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for content length."\n },\n "csp": {\n "description": "Value for csp.",\n "properties": {\n "directives": {\n "description": "Value for directives.",\n "items": {\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "sources": {\n "description": "Value for sources.",\n "items": {\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "keyword",\n "scheme",\n "approved_origin",\n "external_origin",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for value."\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "name",\n "sources"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "hash_count": {\n "description": "Value for hash count.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nonce_count": {\n "description": "Value for nonce count.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "directives",\n "nonce_count",\n "hash_count"\n ],\n "type": "object"\n },\n "links": {\n "description": "Value for links.",\n "items": {\n "properties": {\n "as": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for as."\n },\n "crossorigin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for crossorigin."\n },\n "destination_scope": {\n "description": "Value for destination scope.",\n "enum": [\n "approved",\n "outside_policy",\n "unsupported"\n ],\n "type": "string"\n },\n "href": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for href."\n },\n "rel": {\n "description": "Value for rel.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for type."\n }\n },\n "required": [\n "href",\n "destination_scope",\n "rel",\n "as",\n "type",\n "crossorigin"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "mime_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for mime type."\n },\n "policies": {\n "description": "Value for policies.",\n "properties": {\n "coep": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for coep."\n },\n "coop": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for coop."\n },\n "corp": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for corp."\n },\n "permissions_policy_features": {\n "description": "Value for permissions policy features.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "referrer_policy": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for referrer policy."\n },\n "x_content_type_options": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for x content type options."\n }\n },\n "required": [\n "coop",\n "coep",\n "corp",\n "referrer_policy",\n "x_content_type_options",\n "permissions_policy_features"\n ],\n "type": "object"\n },\n "request_id": {\n "description": "Value for request id.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "url",\n "mime_type",\n "content_length",\n "content_encoding",\n "csp",\n "links",\n "policies"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "responses",\n "dom_urls",\n "agent_hints",\n "excluded_dom_urls",\n "headers_allowlisted"\n ],\n "type": "object"\n },\n "network": {\n "description": "Value for network.",\n "properties": {\n "coverage_started_at": {\n "description": "Value for coverage started at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "prior_activity_available": {\n "const": false,\n "description": "Value for prior activity available.",\n "type": "boolean"\n },\n "requests": {\n "description": "Value for requests.",\n "items": {\n "properties": {\n "body_shapes": {\n "description": "Value for body shapes.",\n "properties": {\n "request": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for request."\n },\n "response": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for response."\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "not_approved",\n "included",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "request",\n "response"\n ],\n "type": "object"\n },\n "encoded_data_length": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for encoded data length."\n },\n "initiator": {\n "description": "Value for initiator.",\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for column."\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for line."\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "type",\n "url",\n "line",\n "column"\n ],\n "type": "object"\n },\n "method": {\n "description": "Value for method.",\n "type": "string"\n },\n "mime_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for mime type."\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "request_id": {\n "description": "Value for request id.",\n "type": "string"\n },\n "resource_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for resource type."\n },\n "status": {\n "anyOf": [\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Current lifecycle or verification status."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "url",\n "origin",\n "method",\n "resource_type",\n "status",\n "mime_type",\n "encoded_data_length",\n "initiator",\n "body_shapes"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "websocket_events": {\n "description": "Value for websocket events.",\n "items": {\n "properties": {\n "direction": {\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "sent",\n "received"\n ],\n "type": "string"\n },\n "opcode": {\n "description": "Value for opcode.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_bytes": {\n "description": "Byte count for payload.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_shape": {\n "anyOf": [\n {\n "properties": {\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "json",\n "text",\n "binary"\n ],\n "type": "string"\n },\n "json_shape": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for json shape."\n }\n },\n "required": [\n "format",\n "json_shape"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for payload shape."\n },\n "request_id": {\n "description": "Value for request id.",\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "direction",\n "opcode",\n "payload_bytes",\n "payload_shape"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "requests",\n "websocket_events",\n "coverage_started_at",\n "prior_activity_available"\n ],\n "type": "object"\n },\n "resources": {\n "description": "Value for resources.",\n "items": {\n "properties": {\n "content_size": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for content size."\n },\n "mime_type": {\n "description": "Value for mime type.",\n "type": "string"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "resource_key": {\n "description": "Value for resource key.",\n "pattern": "^res_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "resource_key",\n "url",\n "origin",\n "type",\n "mime_type",\n "content_size"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "scripts": {\n "description": "Value for scripts.",\n "properties": {\n "items": {\n "description": "Value for items.",\n "items": {\n "properties": {\n "cdp_hash": {\n "description": "Value for cdp hash.",\n "type": "string"\n },\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for frame id."\n },\n "is_module": {\n "description": "Whether is module.",\n "type": "boolean"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for language."\n },\n "length": {\n "description": "Value for length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "resource_reconciliation": {\n "description": "Value for resource reconciliation.",\n "oneOf": [\n {\n "properties": {\n "resource_key": {\n "description": "Value for resource key.",\n "type": "string"\n },\n "status": {\n "const": "exact",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n }\n },\n "required": [\n "status",\n "resource_key"\n ],\n "type": "object"\n },\n {\n "properties": {\n "candidate_resource_keys": {\n "description": "Value for candidate resource keys.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "ambiguous",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n }\n },\n "required": [\n "status",\n "candidate_resource_keys"\n ],\n "type": "object"\n },\n {\n "properties": {\n "reason": {\n "const": "no_exact_sanitized_url",\n "description": "Value for reason.",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "script_key": {\n "description": "Value for script key.",\n "pattern": "^scr_[a-f0-9]{64}(?:_[0-9]+)?$",\n "type": "string"\n },\n "source": {\n "description": "Value for source.",\n "oneOf": [\n {\n "properties": {\n "included": {\n "const": false,\n "description": "Value for included.",\n "type": "boolean"\n },\n "reason": {\n "description": "Value for reason.",\n "type": "string"\n }\n },\n "required": [\n "included",\n "reason"\n ],\n "type": "object"\n },\n {\n "properties": {\n "artifact": {\n "description": "Value for artifact.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "description": "Value for charset.",\n "type": "string"\n },\n "media_type": {\n "description": "Value for media type.",\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n "included": {\n "const": true,\n "description": "Value for included.",\n "type": "boolean"\n }\n },\n "required": [\n "included",\n "artifact"\n ],\n "type": "object"\n }\n ]\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for source map url."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "url",\n "origin",\n "cdp_hash",\n "length",\n "is_module",\n "language",\n "source_map_url",\n "resource_reconciliation",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "items"\n ],\n "type": "object"\n },\n "storage": {\n "description": "Value for storage.",\n "properties": {\n "cache_names": {\n "description": "Value for cache names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "content_fingerprints": {\n "default": [],\n "description": "Value for content fingerprints.",\n "items": {\n "properties": {\n "complete": {\n "description": "Value for complete.",\n "type": "boolean"\n },\n "identity_sha256": {\n "description": "Exact SHA-256 digest of identity.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "description": "Value for scope.",\n "enum": [\n "cookie",\n "local_storage",\n "session_storage",\n "indexed_db_schema",\n "indexed_db_record",\n "cache_entry"\n ],\n "type": "string"\n },\n "value_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact SHA-256 digest of value."\n }\n },\n "required": [\n "scope",\n "identity_sha256",\n "value_sha256",\n "complete"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "fingerprint_algorithm": {\n "const": "sha256",\n "default": "sha256",\n "description": "Value for fingerprint algorithm.",\n "type": "string"\n },\n "fingerprints_complete": {\n "default": false,\n "description": "Value for fingerprints complete.",\n "type": "boolean"\n },\n "indexed_db_names": {\n "description": "Value for indexed db names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "local_storage_keys": {\n "description": "Value for local storage keys.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "origin": {\n "description": "Value for origin.",\n "type": "string"\n },\n "quota_bytes": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Byte count for quota."\n },\n "session_storage_keys": {\n "description": "Value for session storage keys.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "usage_bytes": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ],\n "description": "Byte count for usage."\n },\n "values_redacted": {\n "const": true,\n "description": "Value for values redacted.",\n "type": "boolean"\n }\n },\n "required": [\n "origin",\n "usage_bytes",\n "quota_bytes",\n "local_storage_keys",\n "session_storage_keys",\n "indexed_db_names",\n "cache_names",\n "values_redacted"\n ],\n "type": "object"\n },\n "target": {\n "description": "Value for target.",\n "properties": {\n "attached": {\n "description": "Value for attached.",\n "type": "boolean"\n },\n "origin": {\n "description": "Value for origin.",\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "type": "string"\n },\n "title": {\n "description": "Value for title.",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "title",\n "url",\n "origin",\n "attached"\n ],\n "type": "object"\n },\n "workers": {\n "description": "Value for workers.",\n "items": {\n "properties": {\n "attached": {\n "description": "Value for attached.",\n "type": "boolean"\n },\n "opener_target_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for opener target id."\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for parent frame id."\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "type": "string"\n },\n "type": {\n "description": "Value for type.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "url",\n "origin",\n "attached"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "browser",\n "target",\n "capture_window",\n "completeness",\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "resources",\n "network",\n "console",\n "workers",\n "metadata",\n "storage",\n "limitations"\n ],\n "type": "object"\n },\n "webmcp": {\n "anyOf": [\n {\n "properties": {\n "browser": {\n "description": "Value for browser.",\n "properties": {\n "js_version": {\n "description": "Value for js version.",\n "type": "string"\n },\n "product": {\n "description": "Value for product.",\n "type": "string"\n },\n "protocol_version": {\n "description": "Value for protocol version.",\n "type": "string"\n },\n "revision": {\n "description": "Value for revision.",\n "type": "string"\n },\n "user_agent": {\n "description": "Value for user agent.",\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "properties": {\n "attach_limited_sections": {\n "description": "Value for attach limited sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "description": "Value for conditions.",\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "description": "Value for dropped events.",\n "properties": {\n "console_events": {\n "description": "Value for console events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "description": "Value for network requests.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "description": "Value for scripts.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "description": "Value for timeline events.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "description": "Value for webmcp tools.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "description": "Value for websocket connections.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "description": "Value for websocket frames.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "description": "Value for excluded.",\n "items": {\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for count."\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "description": "Value for section.",\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "description": "Value for policy filtered sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "description": "Value for unavailable sections.",\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "available",\n "unavailable"\n ],\n "type": "string"\n },\n "target": {\n "description": "Value for target.",\n "properties": {\n "origin": {\n "description": "Value for origin.",\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "url",\n "origin"\n ],\n "type": "object"\n },\n "tools": {\n "description": "Value for tools.",\n "properties": {\n "items": {\n "description": "Value for items.",\n "items": {\n "properties": {\n "annotations": {\n "description": "Value for annotations.",\n "properties": {\n "autosubmit": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for autosubmit."\n },\n "read_only": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for read only."\n },\n "untrusted_content": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for untrusted content."\n }\n },\n "required": [\n "read_only",\n "untrusted_content",\n "autosubmit"\n ],\n "type": "object"\n },\n "declaration_kind": {\n "description": "Value for declaration kind.",\n "enum": [\n "declarative",\n "imperative"\n ],\n "type": "string"\n },\n "description": {\n "description": "Value for description.",\n "type": "string"\n },\n "frame_id": {\n "description": "Value for frame id.",\n "type": "string"\n },\n "frame_url": {\n "description": "Value for frame url.",\n "type": "string"\n },\n "input_schema_shape": {\n "anyOf": [\n {\n "properties": {\n "max_depth_observed": {\n "description": "Maximum permitted depth observed for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_count": {\n "description": "Value for node count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "properties": {\n "description": "Value for properties.",\n "items": {\n "properties": {\n "observations": {\n "description": "Value for observations.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "types": {\n "description": "Value for types.",\n "items": {\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "path",\n "types",\n "observations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_type": {\n "description": "Value for root type.",\n "enum": [\n "object",\n "array",\n "string",\n "number",\n "boolean",\n "null"\n ],\n "type": "string"\n }\n },\n "required": [\n "root_type",\n "node_count",\n "max_depth_observed",\n "properties"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for input schema shape."\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "owner_origin": {\n "description": "Value for owner origin.",\n "type": "string"\n },\n "registration_source": {\n "anyOf": [\n {\n "properties": {\n "column": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for column."\n },\n "line": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for line."\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "line",\n "column"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for registration source."\n },\n "tool_key": {\n "description": "Value for tool key.",\n "pattern": "^webmcp_[a-f0-9]{64}$",\n "type": "string"\n },\n "trust": {\n "const": "page-declared-untrusted",\n "description": "Value for trust.",\n "type": "string"\n }\n },\n "required": [\n "tool_key",\n "name",\n "description",\n "frame_id",\n "frame_url",\n "owner_origin",\n "declaration_kind",\n "input_schema_shape",\n "annotations",\n "registration_source",\n "trust"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "description": "Value for total.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "items"\n ],\n "type": "object"\n }\n },\n "required": [\n "browser",\n "target",\n "status",\n "tools",\n "completeness",\n "limitations"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for webmcp."\n }\n },\n "required": [\n "inspection"\n ],\n "type": "object"\n },\n "before_scenario": {\n "additionalProperties": false,\n "description": "Earlier browser scenario capture.",\n "properties": {\n "browser": {\n "additionalProperties": false,\n "description": "Value for browser.",\n "properties": {\n "cleanup": {\n "description": "Value for cleanup.",\n "enum": [\n "terminated-owned-process",\n "disconnected-external"\n ],\n "type": "string"\n },\n "mode": {\n "description": "Operation mode that selects the requested behavior.",\n "enum": [\n "launch",\n "connect"\n ],\n "type": "string"\n },\n "process_ownership": {\n "description": "Value for process ownership.",\n "enum": [\n "provider-owned",\n "external"\n ],\n "type": "string"\n },\n "product": {\n "description": "Value for product.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "mode",\n "process_ownership",\n "cleanup",\n "product",\n "version"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "duration_ms": {\n "description": "Value for duration ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "events": {\n "additionalProperties": false,\n "description": "Value for events.",\n "properties": {\n "dropped": {\n "description": "Value for dropped.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "items": {\n "description": "Value for items.",\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "console",\n "description": "Value for kind.",\n "type": "string"\n },\n "level": {\n "description": "Value for level.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "level",\n "text",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "page-error",\n "description": "Value for kind.",\n "type": "string"\n },\n "message": {\n "description": "Value for message.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "stack": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for stack."\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "message",\n "stack"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "response",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "maximum": 599,\n "minimum": 100,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "minLength": 1,\n "type": "string"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-failed",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-finished",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "description": "Value for failure.",\n "type": "null"\n },\n "header_names": {\n "description": "Value for header names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-unfinished",\n "description": "Value for kind.",\n "type": "string"\n },\n "method": {\n "description": "Value for method.",\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "capture-ended",\n "cancelled"\n ],\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for redirected from transaction id."\n },\n "resource_type": {\n "description": "Value for resource type.",\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "type": "null"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "body": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_exposed",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "description": "Value for message.",\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "request-failed",\n "response-unfinished",\n "body-read-failed",\n "body-read-timeout",\n "cancelled",\n "capture-ended"\n ],\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "message"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "content": {\n "description": "Value for content.",\n "type": "string"\n },\n "encoding": {\n "const": "base64",\n "description": "Value for encoding.",\n "type": "string"\n },\n "media_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for media type."\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "representation": {\n "description": "Value for representation.",\n "enum": [\n "browser-exposed-request-bytes",\n "browser-decoded-response-bytes"\n ],\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "representation",\n "encoding",\n "content",\n "bytes",\n "sha256",\n "media_type",\n "redacted"\n ],\n "type": "object"\n }\n ],\n "description": "Value for body."\n },\n "headers": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "description": "Value for message.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "message"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "items": {\n "description": "Value for items.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for value."\n }\n },\n "required": [\n "name",\n "value",\n "redacted"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "items"\n ],\n "type": "object"\n }\n ],\n "description": "Value for headers."\n },\n "kind": {\n "const": "network-content",\n "description": "Value for kind.",\n "type": "string"\n },\n "phase": {\n "description": "Current plan or execution phase of the operation.",\n "enum": [\n "request",\n "response"\n ],\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source_event_sequence": {\n "description": "Value for source event sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "description": "Value for transaction id.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "transaction_id",\n "source_event_sequence",\n "phase",\n "headers",\n "body"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "websocket-opened",\n "websocket-closed"\n ],\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "websocket-frame-sent",\n "websocket-frame-received"\n ],\n "type": "string"\n },\n "payload_bytes": {\n "description": "Byte count for payload.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_text": {\n "description": "Value for payload text.",\n "type": "string"\n },\n "payload_type": {\n "const": "text",\n "description": "Value for payload type.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "description": "Value for truncated.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "payload_bytes",\n "truncated",\n "payload_type",\n "payload_text"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "websocket-frame-sent",\n "websocket-frame-received"\n ],\n "type": "string"\n },\n "payload_bytes": {\n "description": "Byte count for payload.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_text": {\n "description": "Value for payload text.",\n "type": "null"\n },\n "payload_type": {\n "const": "binary",\n "description": "Value for payload type.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "description": "Value for truncated.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "payload_bytes",\n "truncated",\n "payload_type",\n "payload_text"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "frame-attached",\n "frame-detached",\n "frame-navigated",\n "worker-created",\n "worker-closed",\n "popup-opened",\n "popup-closed"\n ],\n "type": "string"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact name used by this operation."\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "anyOf": [\n {\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for url."\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "download-cancelled",\n "description": "Value for kind.",\n "type": "string"\n },\n "sequence": {\n "description": "Value for sequence.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "suggested_filename": {\n "description": "Value for suggested filename.",\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "suggested_filename",\n "url"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "retained",\n "dropped",\n "items"\n ],\n "type": "object"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "scenario": {\n "additionalProperties": false,\n "description": "Value for scenario.",\n "properties": {\n "action_count": {\n "description": "Value for action count.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "network_content": {\n "additionalProperties": false,\n "description": "Value for network content.",\n "properties": {\n "header_values": {\n "default": false,\n "description": "Value for header values.",\n "type": "boolean"\n },\n "request_body": {\n "default": false,\n "description": "Value for request body.",\n "type": "boolean"\n },\n "response_body": {\n "default": false,\n "description": "Value for response body.",\n "type": "boolean"\n }\n },\n "type": "object"\n },\n "secret_references": {\n "description": "Value for secret references.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "start_origin": {\n "description": "Value for start origin.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "start_origin",\n "action_count",\n "secret_references"\n ],\n "type": "object"\n },\n "steps": {\n "description": "Value for steps.",\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "description": "Value for action.",\n "minLength": 1,\n "type": "string"\n },\n "after_url": {\n "description": "Value for after url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "artifacts": {\n "additionalProperties": false,\n "description": "Value for artifacts.",\n "properties": {\n "accessibility": {\n "description": "Value for accessibility.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "dom": {\n "description": "Value for dom.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "history": {\n "description": "Value for history.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "current_url": {\n "description": "Value for current url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "length": {\n "description": "Value for length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "navigation_entries": {\n "description": "Value for navigation entries.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "type": {\n "description": "Value for type.",\n "enum": [\n "navigate",\n "reload",\n "back_forward",\n "prerender",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "type",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "length",\n "current_url",\n "navigation_entries"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "screenshot": {\n "description": "Value for screenshot.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "description": "Value for data base64.",\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "description": "Value for media type.",\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "storage": {\n "description": "Value for storage.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "cookies": {\n "description": "Value for cookies.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "local_storage": {\n "description": "Value for local storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "session_storage": {\n "description": "Value for session storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "cookies",\n "local_storage",\n "session_storage"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "description": "Value for url.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "object"\n },\n "before_url": {\n "description": "Value for before url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "elapsed_ms": {\n "description": "Value for elapsed ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "description": "Structured, caller-actionable error when the operation fails.",\n "type": "null"\n },\n "event_sequence_end": {\n "description": "Value for event sequence end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "event_sequence_start": {\n "description": "Value for event sequence start.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "const": "completed",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_index",\n "step_id",\n "action",\n "elapsed_ms",\n "before_url",\n "after_url",\n "event_sequence_start",\n "event_sequence_end",\n "artifacts",\n "completeness",\n "status",\n "error"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "description": "Value for action.",\n "minLength": 1,\n "type": "string"\n },\n "after_url": {\n "description": "Value for after url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "artifacts": {\n "additionalProperties": false,\n "description": "Value for artifacts.",\n "properties": {\n "accessibility": {\n "description": "Value for accessibility.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "dom": {\n "description": "Value for dom.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "description": "Value for text.",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "history": {\n "description": "Value for history.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "current_url": {\n "description": "Value for current url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "length": {\n "description": "Value for length.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "navigation_entries": {\n "description": "Value for navigation entries.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "type": {\n "description": "Value for type.",\n "enum": [\n "navigate",\n "reload",\n "back_forward",\n "prerender",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "type",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "length",\n "current_url",\n "navigation_entries"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "screenshot": {\n "description": "Value for screenshot.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "description": "Value for data base64.",\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "description": "Value for media type.",\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "storage": {\n "description": "Value for storage.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "description": "Value for value.",\n "properties": {\n "cookies": {\n "description": "Value for cookies.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "type": "string"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "local_storage": {\n "description": "Value for local storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "session_storage": {\n "description": "Value for session storage.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "type": "string"\n },\n "value_sha256": {\n "description": "Exact SHA-256 digest of value.",\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "description": "Value for value state.",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "cookies",\n "local_storage",\n "session_storage"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "description": "Value for url.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "description": "Value for state.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "description": "Value for observed.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "description": "Value for retained.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "description": "Value for state.",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "object"\n },\n "before_url": {\n "description": "Value for before url.",\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for origin."\n },\n "query_parameter_names": {\n "description": "Value for query parameter names.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "description": "Value for redacted.",\n "type": "boolean"\n },\n "url": {\n "description": "Value for url.",\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "completeness": {\n "description": "Value for completeness.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "description": "Value for equality eligible.",\n "type": "boolean"\n },\n "missing_sections": {\n "description": "Value for missing sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "description": "Current lifecycle or verification status.",\n "type": "string"\n },\n "truncated_sections": {\n "description": "Value for truncated sections.",\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "elapsed_ms": {\n "description": "Value for elapsed ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "description": "Structured, caller-actionable error when the operation fails.",\n "type": "string"\n },\n "event_sequence_end": {\n "description": "Value for event sequence end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "event_sequence_start": {\n "description": "Value for event sequence start.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "failed",\n "cancelled"\n ],\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "step_index": {\n "description": "Value for step index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_index",\n "step_id",\n "action",\n "elapsed_ms",\n "before_url",\n "after_url",\n "event_sequence_start",\n "event_sequence_end",\n "artifacts",\n "completeness",\n "status",\n "error"\n ],\n "type": "object"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "browser",\n "scenario",\n "duration_ms",\n "steps",\n "events",\n "completeness",\n "limitations"\n ],\n "type": "object"\n },\n "normalization": {\n "additionalProperties": false,\n "description": "Exact-literal normalization policy.",\n "properties": {\n "rules": {\n "default": [],\n "description": "Value for rules.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifacts": {\n "description": "Artifact families whose durable string fields use this rule.",\n "items": {\n "enum": [\n "action_state",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "match": {\n "description": "Exact public literal to replace; regular expressions are not used.",\n "minLength": 1,\n "type": "string"\n },\n "replacement": {\n "description": "Recorded replacement text used in normalized comparison.",\n "type": "string"\n },\n "rule_id": {\n "description": "Stable rule identity; rules execute in rule_id order.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "rule_id",\n "artifacts",\n "match",\n "replacement"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "type": "object"\n }\n },\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "compare_web_captures",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "alignment": {\n "additionalProperties": false,\n "properties": {\n "after_only": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "aligned_steps": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "before_only": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "failures": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "duplicate_before_step_id",\n "duplicate_after_step_id",\n "missing_before_step",\n "missing_after_step",\n "action_mismatch",\n "capture_context_mismatch"\n ],\n "type": "string"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "step_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "step_id",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "aligned",\n "partial",\n "failed"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "aligned_steps",\n "before_only",\n "after_only",\n "failures"\n ],\n "type": "object"\n },\n "artifact_diffs": {\n "additionalProperties": false,\n "properties": {\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total"\n ],\n "type": "object"\n },\n "comparison_kind": {\n "const": "browser_scenario",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "normalization": {\n "additionalProperties": false,\n "properties": {\n "built_in_rules": {\n "items": {\n "enum": [\n "ignore_step_elapsed_ms",\n "ignore_event_sequence",\n "ignore_event_step_index",\n "compare_screenshot_content_digest",\n "compare_normalized_text_content"\n ],\n "type": "string"\n },\n "maxItems": 5,\n "minItems": 5,\n "type": "array"\n },\n "rules": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifacts": {\n "description": "Artifact families whose durable string fields use this rule.",\n "items": {\n "enum": [\n "action_state",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "match": {\n "description": "Exact public literal to replace; regular expressions are not used.",\n "minLength": 1,\n "type": "string"\n },\n "replacement": {\n "description": "Recorded replacement text used in normalized comparison.",\n "type": "string"\n },\n "rule_id": {\n "description": "Stable rule identity; rules execute in rule_id order.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "rule_id",\n "artifacts",\n "match",\n "replacement"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "built_in_rules",\n "rules",\n "sha256"\n ],\n "type": "object"\n },\n "overall_status": {\n "enum": [\n "changed",\n "unchanged",\n "unknown"\n ],\n "type": "string"\n },\n "steps": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "after_action": {\n "minLength": 1,\n "type": "string"\n },\n "after_step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "artifact_diffs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "after_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "after_state": {\n "enum": [\n "captured",\n "not_requested",\n "missing",\n "truncated"\n ],\n "type": "string"\n },\n "artifact": {\n "enum": [\n "action_state",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events"\n ],\n "type": "string"\n },\n "before_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "before_state": {\n "enum": [\n "captured",\n "not_requested",\n "missing",\n "truncated"\n ],\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "changed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "artifact",\n "status",\n "before_state",\n "after_state",\n "before_sha256",\n "after_sha256",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "before_action": {\n "minLength": 1,\n "type": "string"\n },\n "before_step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "changed",\n "unchanged",\n "unknown"\n ],\n "type": "string"\n },\n "step_id": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "step_id",\n "before_step_index",\n "after_step_index",\n "before_action",\n "after_action",\n "status",\n "artifact_diffs"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "comparison_kind",\n "overall_status",\n "normalization",\n "alignment",\n "steps",\n "artifact_diffs",\n "limitations"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "after_target": {\n "additionalProperties": false,\n "properties": {\n "target_id": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "url"\n ],\n "type": "object"\n },\n "before_target": {\n "additionalProperties": false,\n "properties": {\n "target_id": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "url"\n ],\n "type": "object"\n },\n "dimensions": {\n "additionalProperties": false,\n "properties": {\n "accessibility": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ],\n "default": {\n "changes": [],\n "reason": "Dimension was not recorded by this version 1 capture diff.",\n "status": "unknown",\n "total_changes": 0\n }\n },\n "dom_structure": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "metadata": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "network": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "resources": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "scripts": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "storage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ],\n "default": {\n "changes": [],\n "reason": "Dimension was not recorded by this version 1 capture diff.",\n "status": "unknown",\n "total_changes": 0\n }\n },\n "webmcp": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change": {\n "enum": [\n "added",\n "removed",\n "modified"\n ],\n "type": "string"\n },\n "identity": {\n "type": "string"\n }\n },\n "required": [\n "identity",\n "change"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "total_changes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "total_changes",\n "changes",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n },\n "total_changes": {\n "const": 0,\n "type": "number"\n }\n },\n "required": [\n "total_changes",\n "changes",\n "status",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "dom_structure",\n "scripts",\n "resources",\n "network",\n "metadata",\n "webmcp",\n "accessibility",\n "storage"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "overall_status": {\n "enum": [\n "changed",\n "unchanged",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "overall_status",\n "before_target",\n "after_target",\n "dimensions",\n "limitations"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Compare Web Captures"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Capture the current visible viewport of one configured page as an inline, content-addressed PNG artifact. The operation never scrolls, navigates, or evaluates page JavaScript.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222",\n "target_id": "TARGET_ID_FROM_LIST_BROWSER_TARGETS"\n }\n ],\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "capture_web_screenshot",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n },\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "captured_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "completeness": {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "type": "string"\n },\n "target_id": {\n "type": "string"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "url",\n "origin"\n ],\n "type": "object"\n },\n "viewport": {\n "additionalProperties": false,\n "properties": {\n "height": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "width": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "width",\n "height"\n ],\n "type": "object"\n }\n },\n "required": [\n "browser",\n "target",\n "captured_at",\n "viewport",\n "artifact",\n "completeness",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Capture Web Screenshot"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare two self-verifying PNG screenshot artifacts with local pixel metrics. Returns exact changed-pixel ratios and channel deltas without OCR or external services.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "after": {\n "bytes": 70,\n "data_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PzWvWQAAAABJRU5ErkJggg==",\n "media_type": "image/png",\n "sha256": "153cf6c9a526a63053a37b10234c2fd85df38887c2dc0a800d90abfa6631d01c"\n },\n "before": {\n "bytes": 70,\n "data_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PzWvWQAAAABJRU5ErkJggg==",\n "media_type": "image/png",\n "sha256": "153cf6c9a526a63053a37b10234c2fd85df38887c2dc0a800d90abfa6631d01c"\n },\n "channel_threshold": 0\n }\n ],\n "properties": {\n "after": {\n "description": "The later or right-hand observation to compare.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "description": "Value for data base64.",\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "description": "Value for media type.",\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n },\n "before": {\n "description": "The earlier or left-hand observation to compare.",\n "properties": {\n "bytes": {\n "description": "Value for bytes.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "description": "Value for data base64.",\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "description": "Value for media type.",\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n },\n "channel_threshold": {\n "default": 0,\n "description": "Value for channel threshold.",\n "maximum": 255,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "before",\n "after"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "compare_web_screenshots",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "additionalProperties": false,\n "properties": {\n "height": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "width": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "width",\n "height"\n ],\n "type": "object"\n },\n "before": {\n "additionalProperties": false,\n "properties": {\n "height": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "width": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "width",\n "height"\n ],\n "type": "object"\n },\n "changed_pixels": {\n "const": 0,\n "type": "number"\n },\n "changed_ratio": {\n "const": 0,\n "type": "number"\n },\n "channel_threshold": {\n "maximum": 255,\n "minimum": 0,\n "type": "integer"\n },\n "compared_pixels": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "maximum_channel_delta": {\n "maximum": 255,\n "minimum": 0,\n "type": "integer"\n },\n "mean_absolute_channel_delta": {\n "maximum": 255,\n "minimum": 0,\n "type": "number"\n },\n "status": {\n "const": "identical",\n "type": "string"\n }\n },\n "required": [\n "before",\n "after",\n "channel_threshold",\n "limitations",\n "status",\n "compared_pixels",\n "changed_pixels",\n "changed_ratio",\n "maximum_channel_delta",\n "mean_absolute_channel_delta"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "additionalProperties": false,\n "properties": {\n "height": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "width": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "width",\n "height"\n ],\n "type": "object"\n },\n "before": {\n "additionalProperties": false,\n "properties": {\n "height": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "width": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "width",\n "height"\n ],\n "type": "object"\n },\n "changed_pixels": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "changed_ratio": {\n "exclusiveMinimum": 0,\n "maximum": 1,\n "type": "number"\n },\n "channel_threshold": {\n "maximum": 255,\n "minimum": 0,\n "type": "integer"\n },\n "compared_pixels": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "maximum_channel_delta": {\n "exclusiveMinimum": 0,\n "maximum": 255,\n "type": "integer"\n },\n "mean_absolute_channel_delta": {\n "exclusiveMinimum": 0,\n "maximum": 255,\n "type": "number"\n },\n "status": {\n "const": "different",\n "type": "string"\n }\n },\n "required": [\n "before",\n "after",\n "channel_threshold",\n "limitations",\n "status",\n "compared_pixels",\n "changed_pixels",\n "changed_ratio",\n "maximum_channel_delta",\n "mean_absolute_channel_delta"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "additionalProperties": false,\n "properties": {\n "height": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "width": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "width",\n "height"\n ],\n "type": "object"\n },\n "before": {\n "additionalProperties": false,\n "properties": {\n "height": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "width": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "width",\n "height"\n ],\n "type": "object"\n },\n "changed_pixels": {\n "type": "null"\n },\n "changed_ratio": {\n "type": "null"\n },\n "channel_threshold": {\n "maximum": 255,\n "minimum": 0,\n "type": "integer"\n },\n "compared_pixels": {\n "const": 0,\n "type": "number"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "maximum_channel_delta": {\n "type": "null"\n },\n "mean_absolute_channel_delta": {\n "type": "null"\n },\n "status": {\n "const": "dimension_mismatch",\n "type": "string"\n }\n },\n "required": [\n "before",\n "after",\n "channel_threshold",\n "limitations",\n "status",\n "compared_pixels",\n "changed_pixels",\n "changed_ratio",\n "maximum_channel_delta",\n "mean_absolute_channel_delta"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Compare Web Screenshots"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": true,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Run a controlled browser scenario when passive observation cannot exercise the application. Launch the selected executable or connect to a loopback CDP target, provide a start URL and actions, and select storage or artifacts when needed. Environment, empty storage, and a URL-only final capture are defaults. capture.network selects request_body, response_body, and header_values independently; selected content is returned inline with transaction and event references, byte digests, and explicit unavailable states. Credentials and declared secrets are redacted. Agents can run their own local fixture servers to control application responses. Actions run until completion or cancellation unless timeout_ms is set. Results are returned inline.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": true,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": true,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "actions": [\n {\n "action": "wait_for_timeout",\n "duration_ms": 250,\n "step_id": "settle"\n }\n ],\n "browser": {\n "executable_path": "/opt/chromium/chrome",\n "mode": "launch"\n },\n "start_url": {\n "query": [],\n "url": "https://app.example.test/"\n }\n }\n ],\n "properties": {\n "actions": {\n "description": "Required sequence of explicit browser actions.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "const": "goto",\n "description": "Value for action.",\n "type": "string"\n },\n "destination": {\n "additionalProperties": false,\n "description": "Value for destination.",\n "properties": {\n "query": {\n "default": [],\n "description": "Non-empty feature or text query to investigate.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "url": {\n "description": "Value for url.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "url"\n ],\n "type": "object"\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "timeout_ms": {\n "description": "Optional action deadline in milliseconds; omitted means wait until completion or request cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "wait_until": {\n "description": "Value for wait until.",\n "enum": [\n "commit",\n "domcontentloaded",\n "load"\n ],\n "type": "string"\n }\n },\n "required": [\n "step_id",\n "action",\n "destination",\n "wait_until"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "const": "click",\n "description": "Value for action.",\n "type": "string"\n },\n "button": {\n "default": "left",\n "description": "Value for button.",\n "enum": [\n "left",\n "middle",\n "right"\n ],\n "type": "string"\n },\n "click_count": {\n "default": 1,\n "description": "Value for click count.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "locator": {\n "description": "Value for locator.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "test_id",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "role",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "role": {\n "description": "Value for role.",\n "enum": [\n "button",\n "checkbox",\n "combobox",\n "dialog",\n "link",\n "listbox",\n "menuitem",\n "option",\n "radio",\n "slider",\n "spinbutton",\n "switch",\n "tab",\n "textbox"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "role",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "css",\n "description": "Value for kind.",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector"\n ],\n "type": "object"\n }\n ]\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "timeout_ms": {\n "description": "Optional action deadline in milliseconds; omitted means wait until completion or request cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "action",\n "locator"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "const": "fill",\n "description": "Value for action.",\n "type": "string"\n },\n "locator": {\n "description": "Value for locator.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "test_id",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "role",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "role": {\n "description": "Value for role.",\n "enum": [\n "button",\n "checkbox",\n "combobox",\n "dialog",\n "link",\n "listbox",\n "menuitem",\n "option",\n "radio",\n "slider",\n "spinbutton",\n "switch",\n "tab",\n "textbox"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "role",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "css",\n "description": "Value for kind.",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector"\n ],\n "type": "object"\n }\n ]\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "timeout_ms": {\n "description": "Optional action deadline in milliseconds; omitted means wait until completion or request cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "step_id",\n "action",\n "locator",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "const": "press",\n "description": "Value for action.",\n "type": "string"\n },\n "key": {\n "description": "Value for key.",\n "minLength": 1,\n "type": "string"\n },\n "locator": {\n "description": "Value for locator.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "test_id",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "role",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "role": {\n "description": "Value for role.",\n "enum": [\n "button",\n "checkbox",\n "combobox",\n "dialog",\n "link",\n "listbox",\n "menuitem",\n "option",\n "radio",\n "slider",\n "spinbutton",\n "switch",\n "tab",\n "textbox"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "role",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "css",\n "description": "Value for kind.",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector"\n ],\n "type": "object"\n }\n ]\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "timeout_ms": {\n "description": "Optional action deadline in milliseconds; omitted means wait until completion or request cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "action",\n "locator",\n "key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "const": "select_option",\n "description": "Value for action.",\n "type": "string"\n },\n "locator": {\n "description": "Value for locator.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "test_id",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "role",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "role": {\n "description": "Value for role.",\n "enum": [\n "button",\n "checkbox",\n "combobox",\n "dialog",\n "link",\n "listbox",\n "menuitem",\n "option",\n "radio",\n "slider",\n "spinbutton",\n "switch",\n "tab",\n "textbox"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "role",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "css",\n "description": "Value for kind.",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector"\n ],\n "type": "object"\n }\n ]\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "timeout_ms": {\n "description": "Optional action deadline in milliseconds; omitted means wait until completion or request cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "step_id",\n "action",\n "locator",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "description": "Value for action.",\n "enum": [\n "check",\n "uncheck"\n ],\n "type": "string"\n },\n "locator": {\n "description": "Value for locator.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "test_id",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "role",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "role": {\n "description": "Value for role.",\n "enum": [\n "button",\n "checkbox",\n "combobox",\n "dialog",\n "link",\n "listbox",\n "menuitem",\n "option",\n "radio",\n "slider",\n "spinbutton",\n "switch",\n "tab",\n "textbox"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "role",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "css",\n "description": "Value for kind.",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector"\n ],\n "type": "object"\n }\n ]\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "timeout_ms": {\n "description": "Optional action deadline in milliseconds; omitted means wait until completion or request cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "action",\n "locator"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "const": "wait_for",\n "description": "Value for action.",\n "type": "string"\n },\n "locator": {\n "description": "Value for locator.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "test_id",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "role",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "role": {\n "description": "Value for role.",\n "enum": [\n "button",\n "checkbox",\n "combobox",\n "dialog",\n "link",\n "listbox",\n "menuitem",\n "option",\n "radio",\n "slider",\n "spinbutton",\n "switch",\n "tab",\n "textbox"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "role",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "css",\n "description": "Value for kind.",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "selector"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "description": "Value for state.",\n "enum": [\n "attached",\n "detached",\n "visible",\n "hidden"\n ],\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "timeout_ms": {\n "description": "Optional action deadline in milliseconds; omitted means wait until completion or request cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "action",\n "locator",\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "const": "wait_for_timeout",\n "description": "Value for action.",\n "type": "string"\n },\n "duration_ms": {\n "description": "Wait this many milliseconds; the wait ends early on cancellation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_id": {\n "description": "Value for step id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n }\n },\n "required": [\n "step_id",\n "action",\n "duration_ms"\n ],\n "type": "object"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n },\n "browser": {\n "description": "Required launch/connect selection. Launch uses the selected executable and an owned temporary profile; connect uses one loopback CDP endpoint and target.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "executable_path": {\n "description": "Local filesystem path for executable.",\n "minLength": 1,\n "type": "string"\n },\n "headless": {\n "default": true,\n "description": "Run without a visible browser window; defaults to true and can be disabled when the host supports a display.",\n "type": "boolean"\n },\n "mode": {\n "const": "launch",\n "description": "Operation mode that selects the requested behavior.",\n "type": "string"\n }\n },\n "required": [\n "mode",\n "executable_path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "mode": {\n "const": "connect",\n "description": "Operation mode that selects the requested behavior.",\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "mode",\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n }\n ]\n },\n "capture": {\n "additionalProperties": false,\n "default": {\n "after_each_step": [],\n "at_end": [\n "url"\n ],\n "events": [],\n "network": {\n "header_values": false,\n "request_body": false,\n "response_body": false\n }\n },\n "description": "Optional retained artifacts and event families. Defaults to only a final sanitized URL; request screenshots, DOM, accessibility, history, storage, and event capture explicitly.",\n "properties": {\n "after_each_step": {\n "default": [],\n "description": "Value for after each step.",\n "items": {\n "enum": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "at_end": {\n "default": [\n "url"\n ],\n "description": "Value for at end.",\n "items": {\n "enum": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "events": {\n "default": [],\n "description": "Value for events.",\n "items": {\n "enum": [\n "console",\n "page-errors",\n "network",\n "websockets",\n "frames",\n "workers",\n "popups",\n "downloads"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "network": {\n "additionalProperties": false,\n "default": {\n "header_values": false,\n "request_body": false,\n "response_body": false\n },\n "description": "Value for network.",\n "properties": {\n "header_values": {\n "default": false,\n "description": "Value for header values.",\n "type": "boolean"\n },\n "request_body": {\n "default": false,\n "description": "Value for request body.",\n "type": "boolean"\n },\n "response_body": {\n "default": false,\n "description": "Value for response body.",\n "type": "boolean"\n }\n },\n "type": "object"\n }\n },\n "type": "object"\n },\n "environment": {\n "additionalProperties": false,\n "default": {\n "color_scheme": "light",\n "locale": "en-US",\n "reduced_motion": "reduce",\n "service_workers": "block",\n "timezone": "UTC",\n "viewport": {\n "device_scale_factor": 1,\n "height": 720,\n "width": 1280\n }\n },\n "description": "Optional deterministic browser settings. Defaults to 1280x720, en-US, UTC, light mode, reduced motion, and blocked service workers.",\n "properties": {\n "color_scheme": {\n "default": "light",\n "description": "Value for color scheme.",\n "enum": [\n "light",\n "dark",\n "no-preference"\n ],\n "type": "string"\n },\n "locale": {\n "default": "en-US",\n "description": "Value for locale.",\n "minLength": 2,\n "pattern": "^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$",\n "type": "string"\n },\n "reduced_motion": {\n "default": "reduce",\n "description": "Value for reduced motion.",\n "enum": [\n "reduce",\n "no-preference"\n ],\n "type": "string"\n },\n "service_workers": {\n "default": "block",\n "description": "Value for service workers.",\n "enum": [\n "allow",\n "block"\n ],\n "type": "string"\n },\n "timezone": {\n "default": "UTC",\n "description": "Value for timezone.",\n "minLength": 1,\n "pattern": "^[A-Za-z0-9_+-]+(?:\\\\/[A-Za-z0-9_+-]+)*$",\n "type": "string"\n },\n "viewport": {\n "additionalProperties": false,\n "default": {\n "device_scale_factor": 1,\n "height": 720,\n "width": 1280\n },\n "description": "Value for viewport.",\n "properties": {\n "device_scale_factor": {\n "default": 1,\n "description": "Value for device scale factor.",\n "exclusiveMinimum": 0,\n "type": "number"\n },\n "height": {\n "default": 720,\n "description": "Value for height.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "width": {\n "default": 1280,\n "description": "Value for width.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "type": "object"\n }\n },\n "type": "object"\n },\n "secrets": {\n "default": [],\n "description": "Optional environment-variable references for values used in actions, URLs, storage, or replay, and for redacting matching observed text. Secret values are never supplied inline.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "environment_variable": {\n "description": "Value for environment variable.",\n "minLength": 1,\n "type": "string"\n },\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n }\n },\n "required": [\n "secret_id",\n "environment_variable"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "start_url": {\n "additionalProperties": false,\n "description": "Required initial HTTP(S) URL. Query values must be declared separately as public literals or secret references.",\n "properties": {\n "query": {\n "default": [],\n "description": "Non-empty feature or text query to investigate.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "url": {\n "description": "Value for url.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "url"\n ],\n "type": "object"\n },\n "storage": {\n "additionalProperties": false,\n "description": "Optional initial cookies, local storage, or session storage. Defaults to empty; every supplied value and origin must be declared and within the allowed origins.",\n "properties": {\n "cookies": {\n "description": "Value for cookies.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "destination": {\n "additionalProperties": false,\n "description": "Value for destination.",\n "properties": {\n "query": {\n "default": [],\n "description": "Non-empty feature or text query to investigate.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "url": {\n "description": "Value for url.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "url"\n ],\n "type": "object"\n },\n "http_only": {\n "description": "Value for http only.",\n "type": "boolean"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "same_site": {\n "description": "Value for same site.",\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "description": "Value for secure.",\n "type": "boolean"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "name",\n "value",\n "destination",\n "http_only",\n "secure",\n "same_site"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "local_storage": {\n "description": "Value for local storage.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "entries": {\n "description": "Value for entries.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "origin": {\n "description": "Value for origin.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "origin",\n "entries"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "session_storage": {\n "description": "Value for session storage.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "entries": {\n "description": "Value for entries.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "source": {\n "const": "literal",\n "description": "Value for source.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "secret_id": {\n "description": "Value for secret id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "source": {\n "const": "secret",\n "description": "Value for source.",\n "type": "string"\n }\n },\n "required": [\n "source",\n "secret_id"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "name",\n "value"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "origin": {\n "description": "Value for origin.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "origin",\n "entries"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "type": "object"\n }\n },\n "required": [\n "browser",\n "start_url",\n "actions"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "capture_browser_scenario",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "cleanup": {\n "enum": [\n "terminated-owned-process",\n "disconnected-external"\n ],\n "type": "string"\n },\n "mode": {\n "enum": [\n "launch",\n "connect"\n ],\n "type": "string"\n },\n "process_ownership": {\n "enum": [\n "provider-owned",\n "external"\n ],\n "type": "string"\n },\n "product": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "mode",\n "process_ownership",\n "cleanup",\n "product",\n "version"\n ],\n "type": "object"\n },\n "completeness": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "duration_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "events": {\n "additionalProperties": false,\n "properties": {\n "dropped": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "items": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "console",\n "type": "string"\n },\n "level": {\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text": {\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "level",\n "text",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "page-error",\n "type": "string"\n },\n "message": {\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "stack": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "message",\n "stack"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "type": "null"\n },\n "header_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request",\n "type": "string"\n },\n "method": {\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_type": {\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "type": "null"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "type": "null"\n },\n "header_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "response",\n "type": "string"\n },\n "method": {\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_type": {\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "maximum": 599,\n "minimum": 100,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "minLength": 1,\n "type": "string"\n },\n "header_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-failed",\n "type": "string"\n },\n "method": {\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_type": {\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "type": "null"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "type": "null"\n },\n "header_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-finished",\n "type": "string"\n },\n "method": {\n "minLength": 1,\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_type": {\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "type": "null"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "failure": {\n "type": "null"\n },\n "header_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "const": "request-unfinished",\n "type": "string"\n },\n "method": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "capture-ended",\n "cancelled"\n ],\n "type": "string"\n },\n "redirected_from_transaction_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_type": {\n "minLength": 1,\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "type": "null"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "method",\n "url",\n "resource_type",\n "header_names",\n "kind",\n "status",\n "failure",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "body": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_exposed",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "request-failed",\n "response-unfinished",\n "body-read-failed",\n "body-read-timeout",\n "cancelled",\n "capture-ended"\n ],\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "message"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "content": {\n "type": "string"\n },\n "encoding": {\n "const": "base64",\n "type": "string"\n },\n "media_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "redacted": {\n "type": "boolean"\n },\n "representation": {\n "enum": [\n "browser-exposed-request-bytes",\n "browser-decoded-response-bytes"\n ],\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "captured",\n "type": "string"\n }\n },\n "required": [\n "state",\n "representation",\n "encoding",\n "content",\n "bytes",\n "sha256",\n "media_type",\n "redacted"\n ],\n "type": "object"\n }\n ]\n },\n "headers": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "message"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "redacted": {\n "type": "boolean"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "value",\n "redacted"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "state": {\n "const": "captured",\n "type": "string"\n }\n },\n "required": [\n "state",\n "items"\n ],\n "type": "object"\n }\n ]\n },\n "kind": {\n "const": "network-content",\n "type": "string"\n },\n "phase": {\n "enum": [\n "request",\n "response"\n ],\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source_event_sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "transaction_id": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "transaction_id",\n "source_event_sequence",\n "phase",\n "headers",\n "body"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "websocket-opened",\n "websocket-closed"\n ],\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "websocket-frame-sent",\n "websocket-frame-received"\n ],\n "type": "string"\n },\n "payload_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_text": {\n "type": "string"\n },\n "payload_type": {\n "const": "text",\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "type": "boolean"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "payload_bytes",\n "truncated",\n "payload_type",\n "payload_text"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "websocket-frame-sent",\n "websocket-frame-received"\n ],\n "type": "string"\n },\n "payload_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "payload_text": {\n "type": "null"\n },\n "payload_type": {\n "const": "binary",\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "type": "boolean"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "payload_bytes",\n "truncated",\n "payload_type",\n "payload_text"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "frame-attached",\n "frame-detached",\n "frame-navigated",\n "worker-created",\n "worker-closed",\n "popup-opened",\n "popup-closed"\n ],\n "type": "string"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "url": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "url",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "download-cancelled",\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "suggested_filename": {\n "type": "string"\n },\n "url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "sequence",\n "step_index",\n "kind",\n "suggested_filename",\n "url"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "retained",\n "dropped",\n "items"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "scenario": {\n "additionalProperties": false,\n "properties": {\n "action_count": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "network_content": {\n "additionalProperties": false,\n "properties": {\n "header_values": {\n "default": false,\n "type": "boolean"\n },\n "request_body": {\n "default": false,\n "type": "boolean"\n },\n "response_body": {\n "default": false,\n "type": "boolean"\n }\n },\n "required": [\n "request_body",\n "response_body",\n "header_values"\n ],\n "type": "object"\n },\n "secret_references": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "start_origin": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "start_origin",\n "action_count",\n "secret_references"\n ],\n "type": "object"\n },\n "steps": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n },\n "after_url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "artifacts": {\n "additionalProperties": false,\n "properties": {\n "accessibility": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "dom": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "history": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "current_url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "navigation_entries": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "type": {\n "enum": [\n "navigate",\n "reload",\n "back_forward",\n "prerender",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "type",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "length",\n "current_url",\n "navigation_entries"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "screenshot": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "storage": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "cookies": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "type": "string"\n },\n "http_only": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "path": {\n "type": "string"\n },\n "same_site": {\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "type": "boolean"\n },\n "value_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "type": "string"\n },\n "http_only": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "path": {\n "type": "string"\n },\n "same_site": {\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "type": "boolean"\n },\n "value_sha256": {\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "local_storage": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "session_storage": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "cookies",\n "local_storage",\n "session_storage"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "object"\n },\n "before_url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "completeness": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "type": "null"\n },\n "event_sequence_end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "event_sequence_start": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "const": "completed",\n "type": "string"\n },\n "step_id": {\n "minLength": 1,\n "type": "string"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_index",\n "step_id",\n "action",\n "elapsed_ms",\n "before_url",\n "after_url",\n "event_sequence_start",\n "event_sequence_end",\n "artifacts",\n "completeness",\n "status",\n "error"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "action": {\n "minLength": 1,\n "type": "string"\n },\n "after_url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "artifacts": {\n "additionalProperties": false,\n "properties": {\n "accessibility": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "dom": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "text"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "history": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "current_url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "navigation_entries": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "type": {\n "enum": [\n "navigate",\n "reload",\n "back_forward",\n "prerender",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "type",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "length",\n "current_url",\n "navigation_entries"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "screenshot": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "data_base64": {\n "type": "string"\n },\n "media_type": {\n "const": "image/png",\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "data_base64"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "storage": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "cookies": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "type": "string"\n },\n "http_only": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "path": {\n "type": "string"\n },\n "same_site": {\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "type": "boolean"\n },\n "value_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "domain": {\n "type": "string"\n },\n "http_only": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "path": {\n "type": "string"\n },\n "same_site": {\n "enum": [\n "Strict",\n "Lax",\n "None"\n ],\n "type": "string"\n },\n "secure": {\n "type": "boolean"\n },\n "value_sha256": {\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "type": "string"\n }\n },\n "required": [\n "name",\n "domain",\n "path",\n "secure",\n "http_only",\n "same_site",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "local_storage": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "session_storage": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "value_state": {\n "const": "hashed",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "value_sha256": {\n "type": "null"\n },\n "value_state": {\n "const": "redacted-secret",\n "type": "string"\n }\n },\n "required": [\n "name",\n "value_state",\n "value_sha256"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "cookies",\n "local_storage",\n "session_storage"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "captured",\n "type": "string"\n },\n "value": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "not_requested",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "missing",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "truncated",\n "type": "string"\n }\n },\n "required": [\n "state",\n "observed",\n "retained",\n "reason"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage"\n ],\n "type": "object"\n },\n "before_url": {\n "additionalProperties": false,\n "properties": {\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "query_parameter_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "redacted": {\n "type": "boolean"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "url",\n "origin",\n "query_parameter_names",\n "redacted"\n ],\n "type": "object"\n },\n "completeness": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n },\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "type": "string"\n },\n "event_sequence_end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "event_sequence_start": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "enum": [\n "failed",\n "cancelled"\n ],\n "type": "string"\n },\n "step_id": {\n "minLength": 1,\n "type": "string"\n },\n "step_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_index",\n "step_id",\n "action",\n "elapsed_ms",\n "before_url",\n "after_url",\n "event_sequence_start",\n "event_sequence_end",\n "artifacts",\n "completeness",\n "status",\n "error"\n ],\n "type": "object"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "browser",\n "scenario",\n "duration_ms",\n "steps",\n "events",\n "completeness",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Capture Browser Scenario"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "List every Electron file:// page target from a selected loopback CDP endpoint. Local file paths are canonicalized after symlink resolution. Selecting an endpoint exposes every eligible target and its local path metadata.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9223"\n }\n ],\n "properties": {\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint"\n ],\n "type": "object"\n },\n "kind": "electron-provider",\n "name": "list_electron_targets",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "excluded": {\n "additionalProperties": false,\n "properties": {\n "non_page": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unsupported_url": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unsupported_url",\n "non_page"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "targets": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "file_path": {\n "type": "string"\n },\n "target_id": {\n "type": "string"\n },\n "title": {\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "title",\n "file_path",\n "attached"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "browser",\n "targets",\n "excluded",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Electron Targets"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Passively inspect one Electron file page by supplying its loopback CDP endpoint and target ID. The provider rechecks that the target currently exists at that endpoint and is a local file page. Returns frames, DOM structure, resource paths, and scripts without evaluating renderer JavaScript or invoking Electron APIs. The selected endpoint exposes every local file page and its metadata to this tool.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "include_script_sources": {\n "const": false,\n "default": false,\n "description": "Whether to include script sources in the result.",\n "type": "boolean"\n },\n "observation_ms": {\n "default": 100,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "include_script_sources": {\n "const": true,\n "description": "Whether to include script sources in the result.",\n "type": "boolean"\n },\n "observation_ms": {\n "default": 100,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id",\n "include_script_sources"\n ],\n "type": "object"\n }\n ],\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9223",\n "target_id": "TARGET_ID_FROM_LIST_ELECTRON_TARGETS"\n }\n ],\n "type": "object"\n },\n "kind": "electron-provider",\n "name": "inspect_electron_page",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "capture_window": {\n "additionalProperties": false,\n "properties": {\n "ended_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "observation_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "started_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n }\n },\n "required": [\n "started_at",\n "ended_at",\n "observation_ms"\n ],\n "type": "object"\n },\n "completeness": {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n "dom": {\n "additionalProperties": false,\n "properties": {\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attribute_names": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_name": {\n "type": "string"\n },\n "node_type": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "node_value_length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "parent_index": {\n "maximum": 9007199254740991,\n "minimum": -1,\n "type": "integer"\n }\n },\n "required": [\n "index",\n "parent_index",\n "node_type",\n "node_name",\n "node_value_length",\n "attribute_names"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_nodes",\n "nodes"\n ],\n "type": "object"\n },\n "frames": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "file_path": {\n "type": "string"\n },\n "frame_id": {\n "type": "string"\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "frame_id",\n "parent_frame_id",\n "file_path"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "resources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "content_size": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "file_path": {\n "type": "string"\n },\n "mime_type": {\n "type": "string"\n },\n "resource_key": {\n "pattern": "^electron_resource_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "resource_key",\n "file_path",\n "type",\n "mime_type",\n "content_size"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "scripts": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "cdp_hash": {\n "type": "string"\n },\n "file_path": {\n "type": "string"\n },\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "is_module": {\n "type": "boolean"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "script_key": {\n "pattern": "^electron_script_[a-f0-9]{64}$",\n "type": "string"\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "included": {\n "const": false,\n "type": "boolean"\n },\n "reason": {\n "type": "string"\n }\n },\n "required": [\n "included",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "charset": {\n "const": "utf-8",\n "type": "string"\n },\n "media_type": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "sha256",\n "bytes",\n "media_type",\n "charset",\n "text"\n ],\n "type": "object"\n },\n "included": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "included",\n "artifact"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "script_key",\n "frame_id",\n "file_path",\n "cdp_hash",\n "length",\n "is_module",\n "language",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "items"\n ],\n "type": "object"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "file_path": {\n "type": "string"\n },\n "target_id": {\n "type": "string"\n },\n "title": {\n "type": "string"\n },\n "type": {\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "title",\n "file_path",\n "attached"\n ],\n "type": "object"\n },\n "workers": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "file_path": {\n "type": "string"\n },\n "opener_target_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "parent_frame_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_id": {\n "minLength": 1,\n "type": "string"\n },\n "type": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "type",\n "file_path",\n "attached",\n "opener_target_id",\n "parent_frame_id"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "browser",\n "target",\n "capture_window",\n "completeness",\n "frames",\n "dom",\n "scripts",\n "resources",\n "workers",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Electron Page"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Reconstruct one local ASAR or extracted JavaScript application as an inline application graph without executing it. Returns all recovered graph nodes, edges, semantic relations, limitations, and coverage.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "format": "auto",\n "input_path": "/Applications/Example.app/Contents/Resources/app.asar"\n }\n ],\n "properties": {\n "format": {\n "default": "auto",\n "description": "Declared input artifact format.",\n "enum": [\n "auto",\n "asar",\n "directory"\n ],\n "type": "string"\n },\n "input_path": {\n "description": "Local filesystem path for input.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "input_path"\n ],\n "type": "object"\n },\n "kind": "electron-provider",\n "name": "analyze_javascript_application",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "format": {\n "enum": [\n "asar",\n "directory"\n ],\n "type": "string"\n },\n "graph": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "edge_id": {\n "pattern": "^jag_edge_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "relationship-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n },\n "relation": {\n "enum": [\n "contains",\n "loads",\n "imports",\n "maps_to",\n "exposes",\n "sends",\n "invokes",\n "handles",\n "calls",\n "persists_to",\n "observed_as",\n "changed_from"\n ],\n "type": "string"\n },\n "source_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "target_node_id",\n "relation",\n "properties",\n "evidence",\n "edge_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "identity": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "global-exact",\n "type": "string"\n },\n "strategy": {\n "const": "content-digest",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "original_source": {\n "type": "string"\n },\n "source_map_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "stability": {\n "const": "source-map-exact",\n "type": "string"\n },\n "strategy": {\n "const": "source-map-original",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "source_map_sha256",\n "original_source",\n "source_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "canonical-path",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "minLength": 1,\n "type": "string"\n },\n "basis": {\n "items": {\n "enum": [\n "syntax-tree",\n "control-flow",\n "imports",\n "exports",\n "literals",\n "api-usage"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "fingerprint_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "cross-version-inference",\n "type": "string"\n },\n "strategy": {\n "const": "structural-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "algorithm",\n "fingerprint_sha256",\n "basis"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "key": {\n "type": "string"\n },\n "namespace": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "artifact-local-key",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "namespace",\n "key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_key": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "capture-only",\n "type": "string"\n },\n "strategy": {\n "const": "runtime-instance",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "capture_sha256",\n "runtime_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observation_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "observation-only",\n "type": "string"\n },\n "strategy": {\n "const": "observation-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "observation_sha256",\n "scope"\n ],\n "type": "object"\n }\n ]\n },\n "kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "observations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "observation-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "label": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "observation_id": {\n "pattern": "^jag_observation_[a-f0-9]{64}$",\n "type": "string"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "label",\n "properties",\n "evidence",\n "observation_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "kind",\n "identity",\n "observations"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "root_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "schema": {\n "const": "JavaScriptApplicationGraph",\n "type": "string"\n }\n },\n "required": [\n "schema",\n "root_node_ids",\n "nodes",\n "edges",\n "coverage",\n "limitations",\n "graph_id"\n ],\n "type": "object"\n },\n "input_path": {\n "minLength": 1,\n "type": "string"\n },\n "inventory_graph_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "inventory_manifest_id": {\n "pattern": "^agm_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "root_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "semantic_graph": {\n "additionalProperties": false,\n "properties": {\n "application_graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "families": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "family": {\n "enum": [\n "boundary",\n "call-flow",\n "child-process",\n "closure",\n "configuration",\n "data-flow",\n "event",\n "object-flow",\n "promise-ownership",\n "request",\n "resource-lifecycle",\n "timer"\n ],\n "type": "string"\n },\n "omitted_relations": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "retained_relations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unsupported"\n ],\n "type": "string"\n },\n "unknown_ids": {\n "items": {\n "pattern": "^jsrg_unknown_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "family",\n "status",\n "retained_relations",\n "omitted_relations",\n "unknown_ids"\n ],\n "type": "object"\n },\n "maxItems": 12,\n "minItems": 12,\n "type": "array"\n },\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_nodes": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "omitted_relations": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_nodes",\n "omitted_relations",\n "limits",\n "families"\n ],\n "type": "object"\n },\n "fingerprints": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "const": "rea.javascript-semantic-function",\n "type": "string"\n },\n "components": {\n "additionalProperties": false,\n "properties": {\n "control_flow_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "effects": {\n "items": {\n "enum": [\n "async",\n "child-process",\n "event",\n "network",\n "promise",\n "resource",\n "timer"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "literal_set_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "normalized_ast_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameter_arity": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "relation_shape_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "parameter_arity",\n "normalized_ast_sha256",\n "control_flow_sha256",\n "relation_shape_sha256",\n "literal_set_sha256",\n "effects"\n ],\n "type": "object"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "fingerprint_id": {\n "pattern": "^jsrg_fingerprint_[a-f0-9]{64}$",\n "type": "string"\n },\n "fingerprint_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "function_node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "function_node_id",\n "algorithm",\n "status",\n "components",\n "limitations",\n "evidence",\n "fingerprint_id",\n "fingerprint_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "graph_id": {\n "pattern": "^jsrg_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "application_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "function_node_id": {\n "anyOf": [\n {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "identity": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "role_key": {\n "minLength": 1,\n "type": "string"\n },\n "source_range": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "artifact_sha256",\n "module_path",\n "source_range",\n "role_key"\n ],\n "type": "object"\n },\n "kind": {\n "enum": [\n "module",\n "function",\n "parameter",\n "binding",\n "expression",\n "literal",\n "property-slot",\n "call-site",\n "return-site",\n "closure-capture",\n "promise",\n "task",\n "event",\n "listener",\n "timer",\n "child-process",\n "stdio",\n "signal",\n "config-source",\n "request",\n "response",\n "boundary",\n "resource",\n "unknown-frontier"\n ],\n "type": "string"\n },\n "label": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "kind",\n "identity",\n "function_node_id",\n "application_node_ids",\n "label",\n "properties",\n "evidence",\n "node_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "relations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "relationship-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n },\n "relation": {\n "enum": [\n "acquires",\n "aggregates",\n "aliases",\n "argument-to-parameter",\n "awaits",\n "calls",\n "cancels-timer",\n "captures",\n "chains",\n "coerces",\n "connects-stdio",\n "consumed-by",\n "constructs-request",\n "creates-promise",\n "defaults",\n "defines",\n "destructures",\n "detaches-task",\n "dispatches-candidate",\n "forwards-signal",\n "listens-error",\n "listens-exit",\n "owns",\n "overrides",\n "parses",\n "reads",\n "reads-argv",\n "reads-config",\n "reads-environment",\n "reads-property",\n "registers-listener",\n "releases",\n "removes-listener",\n "returns-task",\n "returns-to-call",\n "schedules-timer",\n "spawns",\n "spreads",\n "supplies-argv",\n "supplies-env",\n "supplies-request-field",\n "validates",\n "writes",\n "writes-property"\n ],\n "type": "string"\n },\n "relation_id": {\n "pattern": "^jsrg_relation_[a-f0-9]{64}$",\n "type": "string"\n },\n "resolution": {\n "enum": [\n "resolved",\n "candidate"\n ],\n "type": "string"\n },\n "source_node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "target_node_id",\n "relation",\n "resolution",\n "properties",\n "evidence",\n "relation_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "root_node_ids": {\n "items": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "schema": {\n "const": "JavaScriptSemanticRelationGraph",\n "type": "string"\n },\n "unknowns": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "candidate_node_ids": {\n "items": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "family": {\n "enum": [\n "boundary",\n "call-flow",\n "child-process",\n "closure",\n "configuration",\n "data-flow",\n "event",\n "object-flow",\n "promise-ownership",\n "request",\n "resource-lifecycle",\n "timer"\n ],\n "type": "string"\n },\n "node_id": {\n "anyOf": [\n {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "ambiguous-target",\n "bound-reached",\n "dynamic-call",\n "dynamic-property",\n "dynamic-scope",\n "eval-or-generated-code",\n "incomplete-module",\n "missing-source",\n "unsupported-syntax"\n ],\n "type": "string"\n },\n "relation_kinds": {\n "items": {\n "enum": [\n "acquires",\n "aggregates",\n "aliases",\n "argument-to-parameter",\n "awaits",\n "calls",\n "cancels-timer",\n "captures",\n "chains",\n "coerces",\n "connects-stdio",\n "consumed-by",\n "constructs-request",\n "creates-promise",\n "defaults",\n "defines",\n "destructures",\n "detaches-task",\n "dispatches-candidate",\n "forwards-signal",\n "listens-error",\n "listens-exit",\n "owns",\n "overrides",\n "parses",\n "reads",\n "reads-argv",\n "reads-config",\n "reads-environment",\n "reads-property",\n "registers-listener",\n "releases",\n "removes-listener",\n "returns-task",\n "returns-to-call",\n "schedules-timer",\n "spawns",\n "spreads",\n "supplies-argv",\n "supplies-env",\n "supplies-request-field",\n "validates",\n "writes",\n "writes-property"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "unknown_id": {\n "pattern": "^jsrg_unknown_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "family",\n "relation_kinds",\n "reason",\n "detail",\n "candidate_node_ids",\n "evidence",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "schema",\n "root_artifact_sha256",\n "application_graph_id",\n "root_node_ids",\n "nodes",\n "relations",\n "fingerprints",\n "unknowns",\n "coverage",\n "limitations",\n "graph_id"\n ],\n "type": "object"\n },\n "statistics": {\n "additionalProperties": false,\n "properties": {\n "findings": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "invalid_utf8_files": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "modules": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nested_asar_containers": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "parse_failures": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "parsed_javascript_files": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "relevant_files": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "text_bytes_read": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated_scopes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "visited_ast_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "relevant_files",\n "nested_asar_containers",\n "text_bytes_read",\n "invalid_utf8_files",\n "parsed_javascript_files",\n "visited_ast_nodes",\n "findings",\n "modules",\n "parse_failures",\n "truncated_scopes"\n ],\n "type": "object"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "browser_windows": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "context_bridge_apis": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "explicit_web_preferences": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "exposed_api_members": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "ipc": {\n "additionalProperties": false,\n "properties": {\n "ambiguous_renderer_transmissions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "dynamic_channel_operations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "literal_channels": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "main_handlers": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "operations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "paired_renderer_transmissions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "renderer_listeners": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "renderer_transmissions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unpaired_literal_renderer_transmissions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "operations",\n "literal_channels",\n "dynamic_channel_operations",\n "renderer_transmissions",\n "renderer_listeners",\n "main_handlers",\n "paired_renderer_transmissions",\n "ambiguous_renderer_transmissions",\n "unpaired_literal_renderer_transmissions"\n ],\n "type": "object"\n },\n "native_addon_bindings": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "preload_entrypoints": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "resolved_native_addon_bindings": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "resolved_utility_entrypoints": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sender_validation_observations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "utility_processes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "browser_windows",\n "explicit_web_preferences",\n "preload_entrypoints",\n "context_bridge_apis",\n "exposed_api_members",\n "ipc",\n "sender_validation_observations",\n "utility_processes",\n "resolved_utility_entrypoints",\n "native_addon_bindings",\n "resolved_native_addon_bindings"\n ],\n "type": "object"\n }\n },\n "required": [\n "input_path",\n "format",\n "root_artifact_sha256",\n "inventory_manifest_id",\n "inventory_graph_sha256",\n "graph",\n "summary",\n "statistics",\n "limitations",\n "semantic_graph"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Analyze Javascript Application"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Reconcile verified static JavaScript application graphs with existing passive web/Electron CDP, passive V8 Inspector, or provider-owned active Electron Evidence. Active Electron captures contribute an explicitly partial target-only runtime record; they never invent renderer scripts, frames, workers, or execution claims. Exact captured-source digests take priority over caller-declared file/URL mappings; target, frame, script, and worker ambiguity remains explicit, and source-map authority stays separate.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "runtime_observations": [\n {\n "authority": "external-service",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "browser": {\n "js_version": "1",\n "product": "Electron/example",\n "protocol_version": "1.3",\n "revision": "example",\n "user_agent": "Electron example"\n },\n "capture_window": {\n "ended_at": "2026-07-15T00:00:00.100Z",\n "observation_ms": 100,\n "started_at": "2026-07-15T00:00:00.000Z"\n },\n "completeness": {\n "attach_limited_sections": [],\n "conditions": [\n "complete_within_window"\n ],\n "dropped_events": {\n "console_events": 0,\n "network_requests": 0,\n "scripts": 0,\n "timeline_events": 0,\n "total": 0,\n "webmcp_tools": 0,\n "websocket_connections": 0,\n "websocket_frames": 0\n },\n "excluded": [],\n "policy_filtered_sections": [],\n "status": "complete_within_window",\n "truncated_sections": [],\n "unavailable_sections": []\n },\n "dom": {\n "nodes": [],\n "total_nodes": 0\n },\n "frames": [],\n "limitations": [\n "Example passive capture."\n ],\n "resources": [],\n "scripts": {\n "items": [\n {\n "cdp_hash": "example",\n "file_path": "/Applications/Example.app/Contents/Resources/app/renderer.js",\n "frame_id": null,\n "is_module": true,\n "language": "JavaScript",\n "length": 29,\n "script_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "source": {\n "artifact": {\n "bytes": 29,\n "charset": "utf-8",\n "media_type": "text/javascript",\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "text": "export const example = true;\\n"\n },\n "included": true\n }\n }\n ],\n "total": 1\n },\n "target": {\n "attached": false,\n "file_path": "/Applications/Example.app/Contents/Resources/app/index.html",\n "target_id": "example-target",\n "title": "Example",\n "type": "page"\n },\n "workers": []\n },\n "operation": "inspect_electron_page",\n "parameters": {\n "cdp_endpoint": "http://127.0.0.1:9223",\n "include_script_sources": true,\n "target_id": "example-target"\n },\n "predicate_type": "rea.electron-page-inspection",\n "provider": {\n "id": "rea-cdp-electron",\n "name": "REA Electron file-page CDP observation provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n }\n ],\n "static_layers": [\n {\n "analysis": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "format": "directory",\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [],\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "limitations": [],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "inventory_graph_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "inventory_manifest_id": "agm_2222222222222222222222222222222222222222222222222222222222222222",\n "limitations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "semantic_graph": {\n "application_graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "coverage": {\n "families": [\n {\n "family": "boundary",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "call-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "child-process",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "closure",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "configuration",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "data-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "event",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "object-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "promise-ownership",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "request",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "resource-lifecycle",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "timer",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n }\n ],\n "limits": [],\n "omitted_nodes": 0,\n "omitted_relations": 0,\n "status": "unknown",\n "truncated": false\n },\n "fingerprints": [],\n "graph_id": "jsrg_08c925d2bf4ef56d770e0e67901d5bdfb610744cf001a50d36c9bb6dcb195508",\n "limitations": [\n "Semantic relations are unavailable in this minimal example."\n ],\n "nodes": [\n {\n "application_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "function_node_id": null,\n "identifier_strategy": {\n "stability": "artifact-version",\n "strategy": "semantic-content-sha256"\n },\n "identity": {\n "artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "module_path": "renderer.js",\n "role_key": "example-module",\n "source_range": null\n },\n "kind": "module",\n "label": "renderer.js",\n "node_id": "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a",\n "properties": {}\n }\n ],\n "relations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "root_node_ids": [\n "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a"\n ],\n "schema": "JavaScriptSemanticRelationGraph",\n "unknowns": []\n },\n "statistics": {\n "findings": 0,\n "invalid_utf8_files": 0,\n "modules": 0,\n "nested_asar_containers": 0,\n "parse_failures": 0,\n "parsed_javascript_files": 1,\n "relevant_files": 1,\n "text_bytes_read": 29,\n "truncated_scopes": 0,\n "visited_ast_nodes": 1\n },\n "summary": {\n "browser_windows": 0,\n "context_bridge_apis": 0,\n "explicit_web_preferences": 0,\n "exposed_api_members": 0,\n "ipc": {\n "ambiguous_renderer_transmissions": 0,\n "dynamic_channel_operations": 0,\n "literal_channels": 0,\n "main_handlers": 0,\n "operations": 0,\n "paired_renderer_transmissions": 0,\n "renderer_listeners": 0,\n "renderer_transmissions": 0,\n "unpaired_literal_renderer_transmissions": 0\n },\n "native_addon_bindings": 0,\n "preload_entrypoints": 0,\n "resolved_native_addon_bindings": 0,\n "resolved_utility_entrypoints": 0,\n "sender_validation_observations": 0,\n "utility_processes": 0\n }\n },\n "operation": "analyze_javascript_application",\n "parameters": {},\n "predicate_type": "rea.javascript-application-analysis",\n "provider": {\n "id": "rea-javascript-application",\n "name": "REA JavaScript application analyzer",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "directory",\n "local_path": "/Applications/Example.app/Contents/Resources/app",\n "name": "app"\n }\n }\n }\n ]\n }\n ],\n "properties": {\n "runtime_observations": {\n "description": "Value for runtime observations.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "static_layers": {\n "description": "Value for static layers.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis": {\n "additionalProperties": false,\n "description": "Value for analysis.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "role": {\n "default": "application",\n "description": "Value for role.",\n "enum": [\n "application",\n "cache",\n "assets"\n ],\n "type": "string"\n },\n "runtime_mappings": {\n "default": [],\n "description": "Value for runtime mappings.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_prefix": {\n "default": "",\n "description": "Value for artifact prefix.",\n "type": "string"\n },\n "kind": {\n "const": "file-root",\n "description": "Value for kind.",\n "type": "string"\n },\n "root": {\n "description": "Value for root.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "root"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_prefix": {\n "default": "",\n "description": "Value for artifact prefix.",\n "type": "string"\n },\n "kind": {\n "const": "url-prefix",\n "description": "Value for kind.",\n "type": "string"\n },\n "prefix": {\n "description": "Value for prefix.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "prefix"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "analysis"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "static_layers",\n "runtime_observations"\n ],\n "type": "object"\n },\n "kind": "electron-provider",\n "name": "reconcile_javascript_runtime",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "omitted_graph_items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "omitted_reconciliation_items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "omitted_runtime_entities": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "omitted_static_load_states": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "complete-within-inputs",\n "partial",\n "truncated"\n ],\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_runtime_entities",\n "omitted_reconciliation_items",\n "omitted_static_load_states",\n "omitted_graph_items"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "graph": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "edge_id": {\n "pattern": "^jag_edge_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "relationship-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n },\n "relation": {\n "enum": [\n "contains",\n "loads",\n "imports",\n "maps_to",\n "exposes",\n "sends",\n "invokes",\n "handles",\n "calls",\n "persists_to",\n "observed_as",\n "changed_from"\n ],\n "type": "string"\n },\n "source_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "target_node_id",\n "relation",\n "properties",\n "evidence",\n "edge_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "identity": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "global-exact",\n "type": "string"\n },\n "strategy": {\n "const": "content-digest",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "original_source": {\n "type": "string"\n },\n "source_map_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "stability": {\n "const": "source-map-exact",\n "type": "string"\n },\n "strategy": {\n "const": "source-map-original",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "source_map_sha256",\n "original_source",\n "source_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "canonical-path",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "minLength": 1,\n "type": "string"\n },\n "basis": {\n "items": {\n "enum": [\n "syntax-tree",\n "control-flow",\n "imports",\n "exports",\n "literals",\n "api-usage"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "fingerprint_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "cross-version-inference",\n "type": "string"\n },\n "strategy": {\n "const": "structural-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "algorithm",\n "fingerprint_sha256",\n "basis"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "key": {\n "type": "string"\n },\n "namespace": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "artifact-local-key",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "namespace",\n "key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_key": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "capture-only",\n "type": "string"\n },\n "strategy": {\n "const": "runtime-instance",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "capture_sha256",\n "runtime_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observation_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "observation-only",\n "type": "string"\n },\n "strategy": {\n "const": "observation-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "observation_sha256",\n "scope"\n ],\n "type": "object"\n }\n ]\n },\n "kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "observations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "observation-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "label": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "observation_id": {\n "pattern": "^jag_observation_[a-f0-9]{64}$",\n "type": "string"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "label",\n "properties",\n "evidence",\n "observation_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "kind",\n "identity",\n "observations"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "root_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "schema": {\n "const": "JavaScriptApplicationGraph",\n "type": "string"\n }\n },\n "required": [\n "schema",\n "root_node_ids",\n "nodes",\n "edges",\n "coverage",\n "limitations",\n "graph_id"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "reconciliation_id": {\n "pattern": "^jrr_[a-f0-9]{64}$",\n "type": "string"\n },\n "reconciliations": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "content-and-location",\n "content-sha256",\n "module-source-sha256",\n "artifact-path",\n "operator-file-mapping",\n "operator-url-mapping",\n "none"\n ],\n "type": "string"\n },\n "candidate_static_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "candidate_static_nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "static_layer_id": {\n "pattern": "^jrl_[a-f0-9]{64}$",\n "type": "string"\n },\n "static_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "static_layer_id",\n "static_node_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "entity_kind": {\n "enum": [\n "target",\n "frame",\n "script",\n "worker"\n ],\n "type": "string"\n },\n "reason": {\n "enum": [\n "content-and-location-match",\n "unique-content-match",\n "unique-location-match",\n "ambiguous-static-candidates",\n "captured-content-disagrees-with-static-location",\n "no-static-content-match",\n "no-authorized-location-mapping",\n "runtime-location-unresolved",\n "runtime-frame-unattributed",\n "static-or-runtime-coverage-incomplete",\n "reconciliation-limit-reached"\n ],\n "type": "string"\n },\n "reconciliation_id": {\n "pattern": "^jrr_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "static_layer_id": {\n "pattern": "^jrl_[a-f0-9]{64}$",\n "type": "string"\n },\n "static_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "matched",\n "type": "string"\n }\n },\n "required": [\n "reconciliation_id",\n "entity_kind",\n "runtime_evidence_id",\n "runtime_node_id",\n "basis",\n "confidence",\n "reason",\n "candidate_static_count",\n "candidate_static_nodes",\n "static_layer_id",\n "static_node_id",\n "status"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "content-and-location",\n "content-sha256",\n "module-source-sha256",\n "artifact-path",\n "operator-file-mapping",\n "operator-url-mapping",\n "none"\n ],\n "type": "string"\n },\n "candidate_static_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "candidate_static_nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "static_layer_id": {\n "pattern": "^jrl_[a-f0-9]{64}$",\n "type": "string"\n },\n "static_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "static_layer_id",\n "static_node_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "entity_kind": {\n "enum": [\n "target",\n "frame",\n "script",\n "worker"\n ],\n "type": "string"\n },\n "reason": {\n "enum": [\n "content-and-location-match",\n "unique-content-match",\n "unique-location-match",\n "ambiguous-static-candidates",\n "captured-content-disagrees-with-static-location",\n "no-static-content-match",\n "no-authorized-location-mapping",\n "runtime-location-unresolved",\n "runtime-frame-unattributed",\n "static-or-runtime-coverage-incomplete",\n "reconciliation-limit-reached"\n ],\n "type": "string"\n },\n "reconciliation_id": {\n "pattern": "^jrr_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "static_layer_id": {\n "type": "null"\n },\n "static_node_id": {\n "type": "null"\n },\n "status": {\n "enum": [\n "ambiguous",\n "unmatched",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "reconciliation_id",\n "entity_kind",\n "runtime_evidence_id",\n "runtime_node_id",\n "basis",\n "confidence",\n "reason",\n "candidate_static_count",\n "candidate_static_nodes",\n "static_layer_id",\n "static_node_id",\n "status"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "runtime_captures": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "enum": [\n "browser",\n "electron",\n "v8-inspector",\n "electron-active"\n ],\n "type": "string"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts_complete_within_scope": {\n "type": "boolean"\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n },\n "target_location": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "unresolved",\n "type": "string"\n },\n "reason": {\n "const": "unverifiable-file-location",\n "type": "string"\n },\n "reported_url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "reported_url",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "target_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "workers": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "evidence_id",\n "capture_sha256",\n "kind",\n "target_node_id",\n "target_key",\n "target_location",\n "frames",\n "scripts",\n "workers",\n "scripts_complete_within_scope"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "source_map_authority": {\n "additionalProperties": false,\n "properties": {\n "limitation": {\n "minLength": 1,\n "type": "string"\n },\n "runtime_script_declarations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "static_layer_count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "used_for_primary_matching": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "used_for_primary_matching",\n "static_layer_count",\n "runtime_script_declarations",\n "limitation"\n ],\n "type": "object"\n },\n "static_layers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "enum": [\n "asar",\n "directory"\n ],\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "input_path": {\n "minLength": 1,\n "type": "string"\n },\n "layer_id": {\n "pattern": "^jrl_[a-f0-9]{64}$",\n "type": "string"\n },\n "role": {\n "enum": [\n "application",\n "cache",\n "assets"\n ],\n "type": "string"\n },\n "root_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_mappings": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_prefix": {\n "default": "",\n "type": "string"\n },\n "kind": {\n "const": "file-root",\n "type": "string"\n },\n "root": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "root",\n "artifact_prefix"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_prefix": {\n "default": "",\n "type": "string"\n },\n "kind": {\n "const": "url-prefix",\n "type": "string"\n },\n "prefix": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "prefix",\n "artifact_prefix"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "layer_id",\n "role",\n "evidence_id",\n "graph_id",\n "root_artifact_sha256",\n "input_path",\n "format",\n "runtime_mappings"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "static_load_states": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "enum": [\n "javascript-asset",\n "javascript-chunk",\n "javascript-module"\n ],\n "type": "string"\n },\n "reason": {\n "enum": [\n "runtime-script-correspondence",\n "containing-asset-was-loaded",\n "not-observed-in-bounded-capture",\n "layer-outside-runtime-scope",\n "static-or-runtime-coverage-incomplete"\n ],\n "type": "string"\n },\n "runtime_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "static_layer_id": {\n "pattern": "^jrl_[a-f0-9]{64}$",\n "type": "string"\n },\n "static_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "loaded",\n "resident-in-loaded-asset",\n "not-observed-in-capture",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "static_layer_id",\n "static_node_id",\n "kind",\n "status",\n "runtime_node_ids",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "ambiguous": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "matched": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "runtime_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "runtime_scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "runtime_targets": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "runtime_workers": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "static_loaded": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "static_not_observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "static_resident": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "static_unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unmatched": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "runtime_targets",\n "runtime_frames",\n "runtime_scripts",\n "runtime_workers",\n "matched",\n "ambiguous",\n "unmatched",\n "unknown",\n "static_loaded",\n "static_resident",\n "static_not_observed",\n "static_unknown"\n ],\n "type": "object"\n }\n },\n "required": [\n "reconciliation_id",\n "static_layers",\n "runtime_captures",\n "graph",\n "summary",\n "reconciliations",\n "static_load_states",\n "source_map_authority",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Reconcile Javascript Runtime"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": true,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Use this for a provider-owned Electron run when passive CDP or Inspector observation cannot exercise application behavior. REA owns startup and teardown, accepts caller-defined click/wait actions plus window-targeted renderer reload/crash and synthetic open-url/second-instance delivery, and returns correlated window/WebContents/process/preload/session/navigation/shell/IPC evidence without retaining payload values. Results identify observed and unavailable event families, coverage status, action targets, and truncation. External shell, navigation, permission, download, popup, updater, and OS-integration effects are blocked and recorded. Use passive Electron tools for observation-only work.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": true,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": true,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "actions": [\n {\n "kind": "click",\n "selector": "#run",\n "step_id": "exercise-ipc"\n }\n ],\n "application_path": "/Applications/Example.app/Contents/Resources/main.js",\n "args": [],\n "executable_path": "/Applications/Electron.app/Contents/MacOS/Electron"\n }\n ],\n "properties": {\n "actions": {\n "default": [],\n "description": "Value for actions.",\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "click",\n "description": "Value for kind.",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "window_index": {\n "default": 0,\n "description": "Value for window index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "kind",\n "selector"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "duration_ms": {\n "description": "Value for duration ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "wait",\n "description": "Value for kind.",\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "window_index": {\n "description": "Value for window index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "kind",\n "duration_ms"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "renderer-reload",\n "description": "Value for kind.",\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "window_index": {\n "default": 0,\n "description": "Value for window index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "renderer-crash",\n "description": "Value for kind.",\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "window_index": {\n "default": 0,\n "description": "Value for window index.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "step_id",\n "kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "delivery": {\n "description": "Value for delivery.",\n "enum": [\n "open-url",\n "second-instance"\n ],\n "type": "string"\n },\n "kind": {\n "const": "deep-link",\n "description": "Value for kind.",\n "type": "string"\n },\n "step_id": {\n "description": "Value for step id.",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Value for url.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "step_id",\n "kind",\n "delivery",\n "url"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "application_path": {\n "description": "Local filesystem path for application.",\n "minLength": 1,\n "type": "string"\n },\n "application_root": {\n "description": "Local filesystem application root selected for this operation.",\n "minLength": 1,\n "type": "string"\n },\n "args": {\n "default": [],\n "description": "Value for args.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "executable_path": {\n "description": "Local filesystem path for executable.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "executable_path",\n "application_path"\n ],\n "type": "object"\n },\n "kind": "electron-provider",\n "name": "capture_electron_scenario",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "actions": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "type": "null"\n },\n "kind": {\n "enum": [\n "click",\n "wait",\n "renderer-reload",\n "renderer-crash",\n "deep-link"\n ],\n "type": "string"\n },\n "status": {\n "const": "completed",\n "type": "string"\n },\n "step_id": {\n "minLength": 1,\n "type": "string"\n },\n "target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "window_index": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "step_id",\n "kind",\n "window_index",\n "target",\n "elapsed_ms",\n "status",\n "error"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "error": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "click",\n "wait",\n "renderer-reload",\n "renderer-crash",\n "deep-link"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "failed",\n "cancelled"\n ],\n "type": "string"\n },\n "step_id": {\n "minLength": 1,\n "type": "string"\n },\n "target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "window_index": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "step_id",\n "kind",\n "window_index",\n "target",\n "elapsed_ms",\n "status",\n "error"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "application": {\n "additionalProperties": false,\n "properties": {\n "application_path": {\n "minLength": 1,\n "type": "string"\n },\n "cleanup": {\n "const": "terminated-owned-process",\n "type": "string"\n },\n "electron_version": {\n "minLength": 1,\n "type": "string"\n },\n "executable_path": {\n "minLength": 1,\n "type": "string"\n },\n "process_ownership": {\n "const": "provider-owned",\n "type": "string"\n }\n },\n "required": [\n "executable_path",\n "application_path",\n "electron_version",\n "process_ownership",\n "cleanup"\n ],\n "type": "object"\n },\n "coverage": {\n "additionalProperties": false,\n "default": {\n "observed_event_families": [],\n "observed_roles": [],\n "pre_capture_activity": "unavailable",\n "status": "partial_attach",\n "unavailable_event_families": []\n },\n "properties": {\n "observed_event_families": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "observed_roles": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "pre_capture_activity": {\n "const": "unavailable",\n "type": "string"\n },\n "status": {\n "enum": [\n "complete",\n "partial_attach",\n "hook_conflict",\n "target_exited",\n "cleanup_failed"\n ],\n "type": "string"\n },\n "unavailable_event_families": {\n "items": {\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "observed_event_families",\n "unavailable_event_families",\n "observed_roles",\n "pre_capture_activity"\n ],\n "type": "object"\n },\n "ipc": {\n "additionalProperties": false,\n "properties": {\n "events": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "argument_shapes": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "artifact_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "capture_method": {\n "default": "api-wrapper",\n "enum": [\n "api-wrapper",\n "event-emitter",\n "process-hook"\n ],\n "type": "string"\n },\n "channel": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "correlation_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "direction": {\n "anyOf": [\n {\n "enum": [\n "renderer-to-main",\n "main-to-renderer",\n "main-to-utility",\n "utility-to-main"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "error": {\n "type": "boolean"\n },\n "event": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "frame": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "kind": {\n "enum": [\n "main-handler-invocation",\n "main-event-invocation",\n "utility-process-fork",\n "utility-process-message",\n "ipc-main-to-renderer",\n "ipc-utility-to-main",\n "ipc-renderer-send",\n "ipc-renderer-invoke",\n "ipc-renderer-post-message"\n ],\n "type": "string"\n },\n "phase": {\n "anyOf": [\n {\n "enum": [\n "attempted",\n "completed",\n "blocked",\n "failed",\n "observed"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "process_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "receiver": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "result_shape": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sender": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source": {\n "default": "electron-active-hook",\n "type": "string"\n },\n "target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "sequence",\n "correlation_id",\n "kind",\n "channel",\n "frame",\n "argument_shapes",\n "result_shape",\n "process_type",\n "source",\n "capture_method",\n "artifact_path",\n "artifact_sha256",\n "error"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "events",\n "observed"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "processes": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "pid": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "service_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "type": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "pid",\n "type",\n "name",\n "service_name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "items"\n ],\n "type": "object"\n },\n "timeline": {\n "additionalProperties": false,\n "default": {\n "events": [],\n "observed": 0\n },\n "properties": {\n "events": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "argument_shapes": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "artifact_path": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "capture_method": {\n "enum": [\n "api-wrapper",\n "event-emitter",\n "process-hook"\n ],\n "type": "string"\n },\n "channel": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "correlation_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "direction": {\n "anyOf": [\n {\n "enum": [\n "renderer-to-main",\n "main-to-renderer",\n "main-to-utility",\n "utility-to-main"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "error": {\n "type": "boolean"\n },\n "event": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "frame": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "enum": [\n "main-handler-invocation",\n "main-event-invocation",\n "utility-process-fork",\n "utility-process-message",\n "ipc-main-to-renderer",\n "ipc-utility-to-main",\n "ipc-renderer-send",\n "ipc-renderer-invoke",\n "ipc-renderer-post-message",\n "app-lifecycle",\n "window-lifecycle",\n "web-contents-lifecycle",\n "navigation",\n "shell-attempt",\n "process-lifecycle",\n "permission",\n "popup-attempt",\n "download",\n "protocol",\n "preload",\n "native-addon",\n "updater",\n "error"\n ],\n "type": "string"\n },\n "phase": {\n "enum": [\n "attempted",\n "completed",\n "blocked",\n "failed",\n "observed"\n ],\n "type": "string"\n },\n "process_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "receiver": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "result_shape": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sender": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "source": {\n "type": "string"\n },\n "target": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "sequence",\n "correlation_id",\n "kind",\n "event",\n "phase",\n "channel",\n "direction",\n "sender",\n "receiver",\n "frame",\n "target",\n "argument_shapes",\n "result_shape",\n "process_type",\n "source",\n "capture_method",\n "artifact_path",\n "artifact_sha256",\n "error"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "events",\n "observed"\n ],\n "type": "object"\n },\n "windows": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "destroyed": {\n "type": "boolean"\n },\n "title": {\n "type": "string"\n },\n "url": {\n "type": "string"\n },\n "visible": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "web_contents_id": {\n "type": "string"\n },\n "window_id": {\n "type": "string"\n }\n },\n "required": [\n "window_id",\n "web_contents_id",\n "url",\n "title",\n "visible",\n "destroyed"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "application",\n "actions",\n "windows",\n "processes",\n "ipc",\n "timeline",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Capture Electron Scenario"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "List every eligible Node/Electron V8 Inspector target from an explicit literal-loopback endpoint. Node discovery file locations that cannot be verified are preserved as unresolved reported URLs; they do not prove filesystem identity. Listing does not attach to targets.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "inspector_endpoint": "http://127.0.0.1:9229"\n }\n ],\n "properties": {\n "inspector_endpoint": {\n "description": "Value for inspector endpoint.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "inspector_endpoint"\n ],\n "type": "object"\n },\n "kind": "runtime-provider",\n "name": "list_javascript_runtime_targets",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "excluded": {\n "additionalProperties": false,\n "properties": {\n "unconnectable": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unsupported_location": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unsupported_location",\n "unconnectable"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "runtime": {\n "additionalProperties": false,\n "properties": {\n "product": {\n "minLength": 1,\n "type": "string"\n },\n "protocol_version": {\n "minLength": 1,\n "type": "string"\n },\n "v8_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "product",\n "protocol_version",\n "v8_version"\n ],\n "type": "object"\n },\n "targets": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "location": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "const": "scope-fallback",\n "type": "string"\n },\n "file_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "file_path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "origin": {\n "minLength": 1,\n "type": "string"\n },\n "sanitized_url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "origin",\n "sanitized_url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "builtin",\n "type": "string"\n },\n "specifier": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "specifier"\n ],\n "type": "object"\n }\n ]\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "unresolved",\n "type": "string"\n },\n "reason": {\n "const": "unverifiable-file-location",\n "type": "string"\n },\n "reported_url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "reported_url",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "protocol_type": {\n "minLength": 1,\n "type": "string"\n },\n "target_id": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "protocol_type",\n "attached",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "runtime",\n "targets",\n "excluded",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "List Javascript Runtime Targets"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Attach passively to one Node/Electron V8 Inspector target by supplying its loopback Inspector endpoint and target ID. The provider rechecks target presence and attachability; a Node discovery file location may remain unresolved. Loaded script locations are validated independently from Debugger.scriptParsed. Captures script and Runtime execution-context metadata. REA never evaluates, pauses, resumes, reads source, or instruments the target; require/import edges, EventEmitter activity, and Electron IPC remain explicit unknowns. Reconcile the result with static Application Graph Evidence using reconcile_javascript_runtime. The selected endpoint exposes every target it serves to this tool.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "inspector_endpoint": "http://127.0.0.1:9229",\n "target_id": "TARGET_ID_FROM_LIST_JAVASCRIPT_RUNTIME_TARGETS"\n }\n ],\n "properties": {\n "inspector_endpoint": {\n "description": "Value for inspector endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "observation_ms": {\n "default": 100,\n "description": "Value for observation ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "runtime_kind": {\n "description": "Value for runtime kind.",\n "enum": [\n "node",\n "electron-main",\n "electron-preload",\n "electron-renderer"\n ],\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "inspector_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n "kind": "runtime-provider",\n "name": "observe_javascript_runtime",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "capture": {\n "additionalProperties": false,\n "properties": {\n "events_dropped": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "events_observed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "events_retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "metadata_bytes_retained": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "observation_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "type": "boolean"\n },\n "truncation_reasons": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "observation_ms",\n "events_observed",\n "events_retained",\n "events_dropped",\n "metadata_bytes_retained",\n "truncated",\n "truncation_reasons"\n ],\n "type": "object"\n },\n "directly_observed": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "execution_contexts": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "context_key": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "origin": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "state": {\n "enum": [\n "created",\n "destroyed",\n "cleared"\n ],\n "type": "string"\n }\n },\n "required": [\n "context_key",\n "state",\n "name",\n "origin"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "runtime": {\n "additionalProperties": false,\n "properties": {\n "product": {\n "minLength": 1,\n "type": "string"\n },\n "protocol_version": {\n "minLength": 1,\n "type": "string"\n },\n "v8_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "product",\n "protocol_version",\n "v8_version"\n ],\n "type": "object"\n },\n "scripts": {\n "additionalProperties": false,\n "properties": {\n "excluded": {\n "additionalProperties": false,\n "properties": {\n "invalid_protocol_value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unsupported_location": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unsupported_location",\n "invalid_protocol_value"\n ],\n "type": "object"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "cdp_hash": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "execution_context_key": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_module": {\n "type": "boolean"\n },\n "length": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "const": "scope-fallback",\n "type": "string"\n },\n "file_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "file_path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "origin": {\n "minLength": 1,\n "type": "string"\n },\n "sanitized_url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "origin",\n "sanitized_url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "builtin",\n "type": "string"\n },\n "specifier": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "specifier"\n ],\n "type": "object"\n }\n ]\n },\n "script_key": {\n "pattern": "^v8_script_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "observed-loaded",\n "type": "string"\n }\n },\n "required": [\n "script_key",\n "location",\n "execution_context_key",\n "cdp_hash",\n "length",\n "is_module",\n "status"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "observed_total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "items",\n "observed_total",\n "excluded"\n ],\n "type": "object"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "attached": {\n "type": "boolean"\n },\n "location": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "const": "scope-fallback",\n "type": "string"\n },\n "file_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "file_path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "origin": {\n "minLength": 1,\n "type": "string"\n },\n "sanitized_url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "origin",\n "sanitized_url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "builtin",\n "type": "string"\n },\n "specifier": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "specifier"\n ],\n "type": "object"\n }\n ]\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "unresolved",\n "type": "string"\n },\n "reason": {\n "const": "unverifiable-file-location",\n "type": "string"\n },\n "reported_url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "reported_url",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "protocol_type": {\n "minLength": 1,\n "type": "string"\n },\n "runtime_kind": {\n "anyOf": [\n {\n "enum": [\n "node",\n "electron-main",\n "electron-preload",\n "electron-renderer"\n ],\n "type": "string"\n },\n {\n "const": "unknown",\n "type": "string"\n }\n ]\n },\n "runtime_kind_authority": {\n "enum": [\n "caller-declared-unverified",\n "not-declared"\n ],\n "type": "string"\n },\n "target_id": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "protocol_type",\n "attached",\n "location",\n "runtime_kind",\n "runtime_kind_authority"\n ],\n "type": "object"\n },\n "unavailable_without_instrumentation": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "unknowns": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "runtime",\n "target",\n "capture",\n "scripts",\n "execution_contexts",\n "directly_observed",\n "unavailable_without_instrumentation",\n "unknowns",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Observe Javascript Runtime"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Trace a typed literal seed through every reachable part of an authenticated JavaScript Application Graph supplied as inline Evidence or exact same-session retained references. Original static, native, passive-runtime, inferred, and unknown authorities remain distinct; native addon handoffs never open a provider or execute the application.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "application": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "format": "directory",\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [],\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "limitations": [],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "inventory_graph_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "inventory_manifest_id": "agm_2222222222222222222222222222222222222222222222222222222222222222",\n "limitations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "semantic_graph": {\n "application_graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "coverage": {\n "families": [\n {\n "family": "boundary",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "call-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "child-process",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "closure",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "configuration",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "data-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "event",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "object-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "promise-ownership",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "request",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "resource-lifecycle",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "timer",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n }\n ],\n "limits": [],\n "omitted_nodes": 0,\n "omitted_relations": 0,\n "status": "unknown",\n "truncated": false\n },\n "fingerprints": [],\n "graph_id": "jsrg_08c925d2bf4ef56d770e0e67901d5bdfb610744cf001a50d36c9bb6dcb195508",\n "limitations": [\n "Semantic relations are unavailable in this minimal example."\n ],\n "nodes": [\n {\n "application_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "function_node_id": null,\n "identifier_strategy": {\n "stability": "artifact-version",\n "strategy": "semantic-content-sha256"\n },\n "identity": {\n "artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "module_path": "renderer.js",\n "role_key": "example-module",\n "source_range": null\n },\n "kind": "module",\n "label": "renderer.js",\n "node_id": "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a",\n "properties": {}\n }\n ],\n "relations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "root_node_ids": [\n "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a"\n ],\n "schema": "JavaScriptSemanticRelationGraph",\n "unknowns": []\n },\n "statistics": {\n "findings": 0,\n "invalid_utf8_files": 0,\n "modules": 0,\n "nested_asar_containers": 0,\n "parse_failures": 0,\n "parsed_javascript_files": 1,\n "relevant_files": 1,\n "text_bytes_read": 29,\n "truncated_scopes": 0,\n "visited_ast_nodes": 1\n },\n "summary": {\n "browser_windows": 0,\n "context_bridge_apis": 0,\n "explicit_web_preferences": 0,\n "exposed_api_members": 0,\n "ipc": {\n "ambiguous_renderer_transmissions": 0,\n "dynamic_channel_operations": 0,\n "literal_channels": 0,\n "main_handlers": 0,\n "operations": 0,\n "paired_renderer_transmissions": 0,\n "renderer_listeners": 0,\n "renderer_transmissions": 0,\n "unpaired_literal_renderer_transmissions": 0\n },\n "native_addon_bindings": 0,\n "preload_entrypoints": 0,\n "resolved_native_addon_bindings": 0,\n "resolved_utility_entrypoints": 0,\n "sender_validation_observations": 0,\n "utility_processes": 0\n }\n },\n "operation": "analyze_javascript_application",\n "parameters": {},\n "predicate_type": "rea.javascript-application-analysis",\n "provider": {\n "id": "rea-javascript-application",\n "name": "REA JavaScript application analyzer",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "directory",\n "local_path": "/Applications/Example.app/Contents/Resources/app",\n "name": "app"\n }\n },\n "direction": "both",\n "seed": {\n "case_sensitive": false,\n "kind": "module",\n "match": "exact",\n "value": "renderer.js"\n }\n },\n {\n "application": {\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "kind": "retained-evidence"\n },\n "direction": "both",\n "seed": {\n "case_sensitive": false,\n "kind": "module",\n "match": "exact",\n "value": "renderer.js"\n }\n }\n ],\n "properties": {\n "application": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "retained-evidence",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id"\n ],\n "type": "object"\n }\n ],\n "description": "Value for application."\n },\n "direction": {\n "default": "both",\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "outgoing",\n "incoming",\n "both"\n ],\n "type": "string"\n },\n "native_observations": {\n "default": [],\n "description": "Value for native observations.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "seed": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "case_sensitive": {\n "default": false,\n "description": "Whether text matching distinguishes uppercase and lowercase.",\n "type": "boolean"\n },\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "node-id",\n "route",\n "string",\n "api",\n "channel",\n "module",\n "native-export"\n ],\n "type": "string"\n },\n "match": {\n "anyOf": [\n {\n "enum": [\n "exact",\n "contains"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for match."\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "case_sensitive": {\n "default": false,\n "description": "Whether text matching distinguishes uppercase and lowercase.",\n "type": "boolean"\n },\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "route",\n "string",\n "api",\n "channel",\n "module",\n "native-export"\n ],\n "type": "string"\n },\n "match": {\n "const": "exact",\n "description": "Value for match.",\n "type": "string"\n },\n "value": {\n "const": "",\n "description": "Value for value.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value",\n "match"\n ],\n "type": "object"\n }\n ],\n "description": "Value for seed."\n }\n },\n "required": [\n "seed",\n "application"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "trace_application_feature",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "source_graph_status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "complete-within-source",\n "partial",\n "no-match"\n ],\n "type": "string"\n },\n "total_seed_matches": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "source_graph_status",\n "total_seed_matches"\n ],\n "type": "object"\n },\n "direction": {\n "enum": [\n "outgoing",\n "incoming",\n "both"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "graph": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "edge_id": {\n "pattern": "^jag_edge_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "relationship-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n },\n "relation": {\n "enum": [\n "contains",\n "loads",\n "imports",\n "maps_to",\n "exposes",\n "sends",\n "invokes",\n "handles",\n "calls",\n "persists_to",\n "observed_as",\n "changed_from"\n ],\n "type": "string"\n },\n "source_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "target_node_id",\n "relation",\n "properties",\n "evidence",\n "edge_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "identity": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "global-exact",\n "type": "string"\n },\n "strategy": {\n "const": "content-digest",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "original_source": {\n "type": "string"\n },\n "source_map_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "stability": {\n "const": "source-map-exact",\n "type": "string"\n },\n "strategy": {\n "const": "source-map-original",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "source_map_sha256",\n "original_source",\n "source_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "canonical-path",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "minLength": 1,\n "type": "string"\n },\n "basis": {\n "items": {\n "enum": [\n "syntax-tree",\n "control-flow",\n "imports",\n "exports",\n "literals",\n "api-usage"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "fingerprint_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "cross-version-inference",\n "type": "string"\n },\n "strategy": {\n "const": "structural-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "algorithm",\n "fingerprint_sha256",\n "basis"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "key": {\n "type": "string"\n },\n "namespace": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "artifact-local-key",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "namespace",\n "key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_key": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "capture-only",\n "type": "string"\n },\n "strategy": {\n "const": "runtime-instance",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "capture_sha256",\n "runtime_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observation_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "observation-only",\n "type": "string"\n },\n "strategy": {\n "const": "observation-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "observation_sha256",\n "scope"\n ],\n "type": "object"\n }\n ]\n },\n "kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "observations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "observation-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "label": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "observation_id": {\n "pattern": "^jag_observation_[a-f0-9]{64}$",\n "type": "string"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "label",\n "properties",\n "evidence",\n "observation_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "kind",\n "identity",\n "observations"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "root_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "schema": {\n "const": "JavaScriptApplicationGraph",\n "type": "string"\n }\n },\n "required": [\n "schema",\n "root_node_ids",\n "nodes",\n "edges",\n "coverage",\n "limitations",\n "graph_id"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "native_handoffs": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "export_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "native_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "providers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "recommended_tools": {\n "items": {\n "enum": [\n "open_binary",\n "binary_overview",\n "search_procedures",\n "analyze_function",\n "xrefs"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "requested_exports": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "evidence-linked",\n "requires-provider-analysis"\n ],\n "type": "string"\n }\n },\n "required": [\n "native_node_id",\n "artifact_sha256",\n "artifact_path",\n "export_node_ids",\n "requested_exports",\n "status",\n "providers",\n "evidence_ids",\n "recommended_tools"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "paths": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "authorities": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "contains_inference": {\n "type": "boolean"\n },\n "edge_ids": {\n "items": {\n "pattern": "^jag_edge_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "end_kind": {\n "minLength": 1,\n "type": "string"\n },\n "end_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "path_id": {\n "pattern": "^jatp_[a-f0-9]{64}$",\n "type": "string"\n },\n "start_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path_id",\n "start_node_id",\n "end_node_id",\n "end_kind",\n "node_ids",\n "edge_ids",\n "authorities",\n "contains_inference"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "seed": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "case_sensitive": {\n "default": false,\n "type": "boolean"\n },\n "kind": {\n "enum": [\n "node-id",\n "route",\n "string",\n "api",\n "channel",\n "module",\n "native-export"\n ],\n "type": "string"\n },\n "match": {\n "anyOf": [\n {\n "enum": [\n "exact",\n "contains"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "default": null\n },\n "value": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value",\n "match",\n "case_sensitive"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "case_sensitive": {\n "default": false,\n "type": "boolean"\n },\n "kind": {\n "enum": [\n "route",\n "string",\n "api",\n "channel",\n "module",\n "native-export"\n ],\n "type": "string"\n },\n "match": {\n "const": "exact",\n "type": "string"\n },\n "value": {\n "const": "",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value",\n "match",\n "case_sensitive"\n ],\n "type": "object"\n }\n ]\n },\n "seed_matches": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "node-id",\n "label",\n "identity",\n "property"\n ],\n "type": "string"\n },\n "field": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "minLength": 1,\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "kind",\n "basis",\n "field"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "source_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "source_graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "inferred_facts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "matched_seeds": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "native_handoffs": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "observed_facts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "terminal_paths": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "traced_edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "traced_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unavailable_facts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown_facts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "matched_seeds",\n "traced_nodes",\n "traced_edges",\n "terminal_paths",\n "native_handoffs",\n "observed_facts",\n "inferred_facts",\n "unknown_facts",\n "unavailable_facts"\n ],\n "type": "object"\n },\n "trace_id": {\n "pattern": "^jatr_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "trace_id",\n "source_evidence_id",\n "source_graph_id",\n "seed",\n "direction",\n "seed_matches",\n "graph",\n "paths",\n "native_handoffs",\n "summary",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Application Feature"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Trace static JavaScript data-flow, direct call/return, and closure relations from inline application Evidence or an exact same-session retained reference. Dynamic or unsupported semantics remain explicit unknowns; static reachability never claims runtime execution.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "application": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "format": "directory",\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [],\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "limitations": [],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "inventory_graph_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "inventory_manifest_id": "agm_2222222222222222222222222222222222222222222222222222222222222222",\n "limitations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "semantic_graph": {\n "application_graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "coverage": {\n "families": [\n {\n "family": "boundary",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "call-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "child-process",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "closure",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "configuration",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "data-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "event",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "object-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "promise-ownership",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "request",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "resource-lifecycle",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "timer",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n }\n ],\n "limits": [],\n "omitted_nodes": 0,\n "omitted_relations": 0,\n "status": "unknown",\n "truncated": false\n },\n "fingerprints": [],\n "graph_id": "jsrg_08c925d2bf4ef56d770e0e67901d5bdfb610744cf001a50d36c9bb6dcb195508",\n "limitations": [\n "Semantic relations are unavailable in this minimal example."\n ],\n "nodes": [\n {\n "application_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "function_node_id": null,\n "identifier_strategy": {\n "stability": "artifact-version",\n "strategy": "semantic-content-sha256"\n },\n "identity": {\n "artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "module_path": "renderer.js",\n "role_key": "example-module",\n "source_range": null\n },\n "kind": "module",\n "label": "renderer.js",\n "node_id": "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a",\n "properties": {}\n }\n ],\n "relations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "root_node_ids": [\n "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a"\n ],\n "schema": "JavaScriptSemanticRelationGraph",\n "unknowns": []\n },\n "statistics": {\n "findings": 0,\n "invalid_utf8_files": 0,\n "modules": 0,\n "nested_asar_containers": 0,\n "parse_failures": 0,\n "parsed_javascript_files": 1,\n "relevant_files": 1,\n "text_bytes_read": 29,\n "truncated_scopes": 0,\n "visited_ast_nodes": 1\n },\n "summary": {\n "browser_windows": 0,\n "context_bridge_apis": 0,\n "explicit_web_preferences": 0,\n "exposed_api_members": 0,\n "ipc": {\n "ambiguous_renderer_transmissions": 0,\n "dynamic_channel_operations": 0,\n "literal_channels": 0,\n "main_handlers": 0,\n "operations": 0,\n "paired_renderer_transmissions": 0,\n "renderer_listeners": 0,\n "renderer_transmissions": 0,\n "unpaired_literal_renderer_transmissions": 0\n },\n "native_addon_bindings": 0,\n "preload_entrypoints": 0,\n "resolved_native_addon_bindings": 0,\n "resolved_utility_entrypoints": 0,\n "sender_validation_observations": 0,\n "utility_processes": 0\n }\n },\n "operation": "analyze_javascript_application",\n "parameters": {},\n "predicate_type": "rea.javascript-application-analysis",\n "provider": {\n "id": "rea-javascript-application",\n "name": "REA JavaScript application analyzer",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "directory",\n "local_path": "/Applications/Example.app/Contents/Resources/app",\n "name": "app"\n }\n },\n "query": {\n "direction": "backward-provenance",\n "seed": {\n "kind": "semantic-node",\n "node_id": "jsrg_node_0000000000000000000000000000000000000000000000000000000000000000"\n }\n }\n }\n ],\n "properties": {\n "application": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "retained-evidence",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id"\n ],\n "type": "object"\n }\n ],\n "description": "Value for application."\n },\n "query": {\n "additionalProperties": false,\n "description": "Non-empty feature or text query to investigate.",\n "properties": {\n "allowed_relations": {\n "description": "Value for allowed relations.",\n "items": {\n "enum": [\n "acquires",\n "aggregates",\n "aliases",\n "argument-to-parameter",\n "awaits",\n "calls",\n "cancels-timer",\n "captures",\n "chains",\n "coerces",\n "connects-stdio",\n "consumed-by",\n "constructs-request",\n "creates-promise",\n "defaults",\n "defines",\n "destructures",\n "detaches-task",\n "dispatches-candidate",\n "forwards-signal",\n "listens-error",\n "listens-exit",\n "owns",\n "overrides",\n "parses",\n "reads",\n "reads-argv",\n "reads-config",\n "reads-environment",\n "reads-property",\n "registers-listener",\n "releases",\n "removes-listener",\n "returns-task",\n "returns-to-call",\n "schedules-timer",\n "spawns",\n "spreads",\n "supplies-argv",\n "supplies-env",\n "supplies-request-field",\n "validates",\n "writes",\n "writes-property"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "direction": {\n "description": "Direction in which to traverse or compare relationships.",\n "enum": [\n "backward-provenance",\n "forward-influence",\n "callers",\n "ownership"\n ],\n "type": "string"\n },\n "expected": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "classes": {\n "description": "Value for classes.",\n "items": {\n "enum": [\n "module",\n "function",\n "parameter",\n "binding",\n "expression",\n "literal",\n "property-slot",\n "call-site",\n "return-site",\n "closure-capture",\n "promise",\n "task",\n "event",\n "listener",\n "timer",\n "child-process",\n "stdio",\n "signal",\n "config-source",\n "request",\n "response",\n "boundary",\n "resource",\n "unknown-frontier"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "role": {\n "description": "Value for role.",\n "enum": [\n "source",\n "sink"\n ],\n "type": "string"\n }\n },\n "required": [\n "role",\n "classes"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "default": null,\n "description": "Value for expected."\n },\n "include_ambiguous_dynamic_edges": {\n "default": false,\n "description": "Whether to include ambiguous dynamic edges in the result.",\n "type": "boolean"\n },\n "seed": {\n "description": "Value for seed.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "semantic-node",\n "description": "Value for kind.",\n "type": "string"\n },\n "node_id": {\n "description": "Value for node id.",\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "application-node",\n "description": "Value for kind.",\n "type": "string"\n },\n "node_id": {\n "description": "Value for node id.",\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "literal",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for value."\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "fingerprint_sha256": {\n "description": "Exact SHA-256 digest of fingerprint.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "function",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "fingerprint_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "property",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "endpoint",\n "description": "Value for kind.",\n "type": "string"\n },\n "value": {\n "description": "Value for value.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "event",\n "description": "Value for kind.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "field": {\n "description": "Value for field.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "boundary-field",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "field"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "seed",\n "direction"\n ],\n "type": "object"\n }\n },\n "required": [\n "application",\n "query"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "trace_javascript_semantics",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "status": {\n "enum": [\n "complete",\n "partial",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "status"\n ],\n "type": "object"\n },\n "direction": {\n "enum": [\n "backward-provenance",\n "forward-influence",\n "callers",\n "ownership"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 1,\n "minItems": 1,\n "type": "array"\n },\n "expected_match_node_ids": {\n "items": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "application_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "function_node_id": {\n "anyOf": [\n {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "identity": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "role_key": {\n "minLength": 1,\n "type": "string"\n },\n "source_range": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "artifact_sha256",\n "module_path",\n "source_range",\n "role_key"\n ],\n "type": "object"\n },\n "kind": {\n "enum": [\n "module",\n "function",\n "parameter",\n "binding",\n "expression",\n "literal",\n "property-slot",\n "call-site",\n "return-site",\n "closure-capture",\n "promise",\n "task",\n "event",\n "listener",\n "timer",\n "child-process",\n "stdio",\n "signal",\n "config-source",\n "request",\n "response",\n "boundary",\n "resource",\n "unknown-frontier"\n ],\n "type": "string"\n },\n "label": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "kind",\n "identity",\n "function_node_id",\n "application_node_ids",\n "label",\n "properties",\n "evidence",\n "node_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "query_id": {\n "pattern": "^jsrq_[a-f0-9]{64}$",\n "type": "string"\n },\n "relations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "relationship-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n },\n "relation": {\n "enum": [\n "acquires",\n "aggregates",\n "aliases",\n "argument-to-parameter",\n "awaits",\n "calls",\n "cancels-timer",\n "captures",\n "chains",\n "coerces",\n "connects-stdio",\n "consumed-by",\n "constructs-request",\n "creates-promise",\n "defaults",\n "defines",\n "destructures",\n "detaches-task",\n "dispatches-candidate",\n "forwards-signal",\n "listens-error",\n "listens-exit",\n "owns",\n "overrides",\n "parses",\n "reads",\n "reads-argv",\n "reads-config",\n "reads-environment",\n "reads-property",\n "registers-listener",\n "releases",\n "removes-listener",\n "returns-task",\n "returns-to-call",\n "schedules-timer",\n "spawns",\n "spreads",\n "supplies-argv",\n "supplies-env",\n "supplies-request-field",\n "validates",\n "writes",\n "writes-property"\n ],\n "type": "string"\n },\n "relation_id": {\n "pattern": "^jsrg_relation_[a-f0-9]{64}$",\n "type": "string"\n },\n "resolution": {\n "enum": [\n "resolved",\n "candidate"\n ],\n "type": "string"\n },\n "source_node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "target_node_id",\n "relation",\n "resolution",\n "properties",\n "evidence",\n "relation_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "seed": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "semantic-node",\n "type": "string"\n },\n "node_id": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "application-node",\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "literal",\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "fingerprint_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "function",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "fingerprint_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "property",\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "endpoint",\n "type": "string"\n },\n "value": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "event",\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "field": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "boundary-field",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "field"\n ],\n "type": "object"\n }\n ]\n },\n "seed_node_ids": {\n "items": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "source_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "source_graph_id": {\n "pattern": "^jsrg_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "found",\n "no-match",\n "ambiguous",\n "partial",\n "unsupported"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "relevant_unknowns": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total_seed_matches": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "traversed_functions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "traversed_modules": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "traversed_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "traversed_relations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total_seed_matches",\n "traversed_nodes",\n "traversed_relations",\n "traversed_functions",\n "traversed_modules",\n "relevant_unknowns"\n ],\n "type": "object"\n },\n "unknowns": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "candidate_node_ids": {\n "items": {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "family": {\n "enum": [\n "boundary",\n "call-flow",\n "child-process",\n "closure",\n "configuration",\n "data-flow",\n "event",\n "object-flow",\n "promise-ownership",\n "request",\n "resource-lifecycle",\n "timer"\n ],\n "type": "string"\n },\n "node_id": {\n "anyOf": [\n {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "ambiguous-target",\n "bound-reached",\n "dynamic-call",\n "dynamic-property",\n "dynamic-scope",\n "eval-or-generated-code",\n "incomplete-module",\n "missing-source",\n "unsupported-syntax"\n ],\n "type": "string"\n },\n "relation_kinds": {\n "items": {\n "enum": [\n "acquires",\n "aggregates",\n "aliases",\n "argument-to-parameter",\n "awaits",\n "calls",\n "cancels-timer",\n "captures",\n "chains",\n "coerces",\n "connects-stdio",\n "consumed-by",\n "constructs-request",\n "creates-promise",\n "defaults",\n "defines",\n "destructures",\n "detaches-task",\n "dispatches-candidate",\n "forwards-signal",\n "listens-error",\n "listens-exit",\n "owns",\n "overrides",\n "parses",\n "reads",\n "reads-argv",\n "reads-config",\n "reads-environment",\n "reads-property",\n "registers-listener",\n "releases",\n "removes-listener",\n "returns-task",\n "returns-to-call",\n "schedules-timer",\n "spawns",\n "spreads",\n "supplies-argv",\n "supplies-env",\n "supplies-request-field",\n "validates",\n "writes",\n "writes-property"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "unknown_id": {\n "pattern": "^jsrg_unknown_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "family",\n "relation_kinds",\n "reason",\n "detail",\n "candidate_node_ids",\n "evidence",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "query_id",\n "source_graph_id",\n "seed",\n "direction",\n "status",\n "seed_node_ids",\n "nodes",\n "relations",\n "unknowns",\n "expected_match_node_ids",\n "summary",\n "coverage",\n "limitations",\n "source_evidence_id",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Javascript Semantics"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare two authenticated JavaScript Application Graph versions supplied as inline Evidence or exact same-session retained references. Uses unique-only exact digest, module source digest, source-map identity, structural fingerprint, and non-module semantic-key tiers. Reports added, removed, changed, ambiguous, and unknown entities plus the complete matching changed_from graph without fuzzy or module-ordinal pairing.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "left": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "format": "directory",\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [],\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "limitations": [],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "inventory_graph_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "inventory_manifest_id": "agm_2222222222222222222222222222222222222222222222222222222222222222",\n "limitations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "semantic_graph": {\n "application_graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "coverage": {\n "families": [\n {\n "family": "boundary",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "call-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "child-process",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "closure",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "configuration",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "data-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "event",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "object-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "promise-ownership",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "request",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "resource-lifecycle",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "timer",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n }\n ],\n "limits": [],\n "omitted_nodes": 0,\n "omitted_relations": 0,\n "status": "unknown",\n "truncated": false\n },\n "fingerprints": [],\n "graph_id": "jsrg_08c925d2bf4ef56d770e0e67901d5bdfb610744cf001a50d36c9bb6dcb195508",\n "limitations": [\n "Semantic relations are unavailable in this minimal example."\n ],\n "nodes": [\n {\n "application_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "function_node_id": null,\n "identifier_strategy": {\n "stability": "artifact-version",\n "strategy": "semantic-content-sha256"\n },\n "identity": {\n "artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "module_path": "renderer.js",\n "role_key": "example-module",\n "source_range": null\n },\n "kind": "module",\n "label": "renderer.js",\n "node_id": "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a",\n "properties": {}\n }\n ],\n "relations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "root_node_ids": [\n "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a"\n ],\n "schema": "JavaScriptSemanticRelationGraph",\n "unknowns": []\n },\n "statistics": {\n "findings": 0,\n "invalid_utf8_files": 0,\n "modules": 0,\n "nested_asar_containers": 0,\n "parse_failures": 0,\n "parsed_javascript_files": 1,\n "relevant_files": 1,\n "text_bytes_read": 29,\n "truncated_scopes": 0,\n "visited_ast_nodes": 1\n },\n "summary": {\n "browser_windows": 0,\n "context_bridge_apis": 0,\n "explicit_web_preferences": 0,\n "exposed_api_members": 0,\n "ipc": {\n "ambiguous_renderer_transmissions": 0,\n "dynamic_channel_operations": 0,\n "literal_channels": 0,\n "main_handlers": 0,\n "operations": 0,\n "paired_renderer_transmissions": 0,\n "renderer_listeners": 0,\n "renderer_transmissions": 0,\n "unpaired_literal_renderer_transmissions": 0\n },\n "native_addon_bindings": 0,\n "preload_entrypoints": 0,\n "resolved_native_addon_bindings": 0,\n "resolved_utility_entrypoints": 0,\n "sender_validation_observations": 0,\n "utility_processes": 0\n }\n },\n "operation": "analyze_javascript_application",\n "parameters": {},\n "predicate_type": "rea.javascript-application-analysis",\n "provider": {\n "id": "rea-javascript-application",\n "name": "REA JavaScript application analyzer",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "directory",\n "local_path": "/Applications/Example.app/Contents/Resources/app",\n "name": "app"\n }\n },\n "right": {\n "authority": "analyst-inference",\n "confidence": "inferred",\n "environment": null,\n "evidence_id": "ev_c2754cf75e286b839ed825f247062067a6472acb1e7b6ce0f9aaceec6448d6d7",\n "evidence_links": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54"\n ],\n "limitations": [\n "Artifact identity is unavailable for this observation.",\n "A static module resident in an observed bundle is not reported as executed.",\n "Caller-declared file and URL mappings are inference inputs and do not expand CDP origin or filesystem-root authority.",\n "Digest equality establishes byte identity but not code reachability, module initialization, or feature execution.",\n "Not-observed means absent from the bounded captures in scope, never globally unloaded.",\n "Only caller-supplied, semantically verified Evidence records were reconciled.",\n "Source-map authority remains separate from passive runtime authority."\n ],\n "locations": [],\n "normalized_result": {\n "coverage": {\n "omitted_graph_items": 0,\n "omitted_reconciliation_items": 0,\n "omitted_runtime_entities": 0,\n "omitted_static_load_states": 0,\n "status": "complete-within-inputs",\n "truncated": false\n },\n "evidence_links": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54"\n ],\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [\n {\n "edge_id": "jag_edge_3799eb1c2b0f224013a0edba8911a8b9edabd5fa6da2f893a6cfa24a899c9463",\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "cross-layer-reconciliation",\n "confidence": "high",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-runtime-reconciliation",\n "operation": "reconcile_javascript_runtime",\n "version": "1"\n },\n "limitations": [\n "Equal content digests establish byte identity, not execution causality.",\n "Passive script parsing or target presence does not prove feature execution."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "target_key": "example-target"\n }\n },\n "state": "inferred"\n },\n "identifier_strategy": {\n "stability": "relationship-exact",\n "strategy": "semantic-content-sha256"\n },\n "properties": {\n "basis": "content-and-location",\n "reconciliation_id": "jrr_item_ea9d751c4bf3a2234af56b80d5ac8a1f953da9a9477cd46586a62934ff4a5fb2",\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753"\n },\n "relation": "observed_as",\n "source_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "target_node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e"\n },\n {\n "edge_id": "jag_edge_f3c06b02c5ac77056b5d2a9f8de834ea0a9123b01dc369a011e3adc4a535338b",\n "evidence": {\n "artifact": {\n "available": false,\n "detail": "The relationship is runtime metadata, not standalone bytes.",\n "reason": "not-applicable"\n },\n "authority": "passive-cdp-runtime",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-cdp-electron",\n "operation": "inspect_electron_page",\n "version": "1"\n },\n "limitations": [\n "The relationship was observed only within one bounded passive CDP capture."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "target_key": "example-target"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "relationship-exact",\n "strategy": "semantic-content-sha256"\n },\n "properties": {\n "basis": "passive-cdp-observation"\n },\n "relation": "loads",\n "source_node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "target_node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e"\n }\n ],\n "graph_id": "jag_0536a9584eb91a16f78f71e8bef4cffc3105df18098fe65a7b114add520203ca",\n "limitations": [\n "Cross-layer observed_as edges are inferences; digest equality proves byte identity only.",\n "Runtime presence is bounded to passive capture windows and does not prove feature execution.",\n "Source-map relationships retain their original static authority and are not promoted into runtime matches."\n ],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n },\n {\n "identity": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "runtime_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "stability": "capture-only",\n "strategy": "runtime-instance"\n },\n "kind": "runtime-script-instance",\n "node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "passive-cdp-runtime",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-cdp-electron",\n "operation": "inspect_electron_page",\n "version": "1"\n },\n "limitations": [\n "The entity was observed only within one bounded passive CDP capture."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "target_key": "example-target"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "/Applications/Example.app/Contents/Resources/app/renderer.js",\n "observation_id": "jag_observation_9315403b875ea70c310909daf797780ab9045ff8f4803db38a9184e4f0704f11",\n "properties": {\n "cdp_hash": "example",\n "is_module": true,\n "language": "JavaScript",\n "length": 29,\n "location": "/Applications/Example.app/Contents/Resources/app/renderer.js",\n "source_sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n }\n }\n ]\n },\n {\n "identity": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "runtime_key": "example-target",\n "stability": "capture-only",\n "strategy": "runtime-instance"\n },\n "kind": "target",\n "node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "available": false,\n "detail": "This runtime entity has no standalone artifact bytes.",\n "reason": "not-applicable"\n },\n "authority": "passive-cdp-runtime",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-cdp-electron",\n "operation": "inspect_electron_page",\n "version": "1"\n },\n "limitations": [\n "The entity was observed only within one bounded passive CDP capture."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": null,\n "target_key": "example-target"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "Example",\n "observation_id": "jag_observation_844a03a29af96a0bcd4625e0377b67c439af68a462c5fa83009c7c27283c5dee",\n "properties": {\n "attached": false,\n "location": "/Applications/Example.app/Contents/Resources/app/index.html",\n "runtime_type": "page"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "limitations": [\n "A static module resident in an observed bundle is not reported as executed.",\n "Caller-declared file and URL mappings are inference inputs and do not expand CDP origin or filesystem-root authority.",\n "Digest equality establishes byte identity but not code reachability, module initialization, or feature execution.",\n "Not-observed means absent from the bounded captures in scope, never globally unloaded.",\n "Only caller-supplied, semantically verified Evidence records were reconciled.",\n "Source-map authority remains separate from passive runtime authority."\n ],\n "reconciliation_id": "jrr_ec50425a5a5a5cb85aa40f13f72dcba54a8ab16170d9421a0277fbdf0fa4c9eb",\n "reconciliations": [\n {\n "basis": "none",\n "candidate_static_count": 0,\n "candidate_static_nodes": [],\n "confidence": "unknown",\n "entity_kind": "target",\n "reason": "no-static-content-match",\n "reconciliation_id": "jrr_item_abd5635b06340f082ef46bff8802a361c7b30011085f1c9e7437b29b451c1e5e",\n "runtime_evidence_id": "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "runtime_node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "static_layer_id": null,\n "static_node_id": null,\n "status": "unmatched"\n },\n {\n "basis": "content-and-location",\n "candidate_static_count": 1,\n "candidate_static_nodes": [\n {\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "static_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n }\n ],\n "confidence": "high",\n "entity_kind": "script",\n "reason": "content-and-location-match",\n "reconciliation_id": "jrr_item_ea9d751c4bf3a2234af56b80d5ac8a1f953da9a9477cd46586a62934ff4a5fb2",\n "runtime_evidence_id": "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "runtime_node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e",\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "static_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "status": "matched"\n }\n ],\n "runtime_captures": [\n {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "evidence_id": "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "frames": 0,\n "kind": "electron",\n "scripts": 1,\n "scripts_complete_within_scope": true,\n "target_key": "example-target",\n "target_location": "/Applications/Example.app/Contents/Resources/app/index.html",\n "target_node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "workers": 0\n }\n ],\n "source_map_authority": {\n "limitation": "Source-map declarations and original-source content retain separate static authority and are not used as primary runtime byte matches.",\n "runtime_script_declarations": 0,\n "static_layer_count": 1,\n "used_for_primary_matching": false\n },\n "static_layers": [\n {\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "format": "directory",\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "role": "application",\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "runtime_mappings": []\n }\n ],\n "static_load_states": [\n {\n "kind": "javascript-asset",\n "reason": "runtime-script-correspondence",\n "runtime_node_ids": [\n "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e"\n ],\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "static_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "status": "loaded"\n }\n ],\n "summary": {\n "ambiguous": 0,\n "matched": 1,\n "runtime_frames": 0,\n "runtime_scripts": 1,\n "runtime_targets": 1,\n "runtime_workers": 0,\n "static_loaded": 1,\n "static_not_observed": 0,\n "static_resident": 0,\n "static_unknown": 0,\n "unknown": 0,\n "unmatched": 1\n }\n },\n "operation": "reconcile_javascript_runtime",\n "parameters": {\n "runtime_evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "static_layers": [\n {\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "role": "application",\n "runtime_mappings": []\n }\n ]\n },\n "predicate_type": "rea.javascript-runtime-reconciliation",\n "provider": {\n "id": "rea-javascript-runtime-reconciliation",\n "name": "REA JavaScript runtime reconciliation",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n }\n },\n {\n "left": {\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "kind": "retained-evidence"\n },\n "right": {\n "evidence_id": "ev_c2754cf75e286b839ed825f247062067a6472acb1e7b6ce0f9aaceec6448d6d7",\n "kind": "retained-evidence"\n }\n }\n ],\n "properties": {\n "left": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "retained-evidence",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id"\n ],\n "type": "object"\n }\n ],\n "description": "Left-hand input used for comparison or differential execution."\n },\n "left_native_observations": {\n "default": [],\n "description": "Value for left native observations.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "right": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "retained-evidence",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id"\n ],\n "type": "object"\n }\n ],\n "description": "Right-hand input used for comparison or differential execution."\n },\n "right_native_observations": {\n "default": [],\n "description": "Value for right native observations.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "left",\n "right"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "compare_application_versions",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "comparison_id": {\n "pattern": "^javc_[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "left_graph_omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_graph_status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "right_graph_omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_graph_status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "complete-within-inputs",\n "partial",\n "truncated"\n ],\n "type": "string"\n }\n },\n "required": [\n "left_graph_status",\n "right_graph_status",\n "left_graph_omitted_count",\n "right_graph_omitted_count",\n "status"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "graph": {\n "additionalProperties": false,\n "properties": {\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "edge_id": {\n "pattern": "^jag_edge_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "relationship-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n },\n "relation": {\n "enum": [\n "contains",\n "loads",\n "imports",\n "maps_to",\n "exposes",\n "sends",\n "invokes",\n "handles",\n "calls",\n "persists_to",\n "observed_as",\n "changed_from"\n ],\n "type": "string"\n },\n "source_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "target_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "source_node_id",\n "target_node_id",\n "relation",\n "properties",\n "evidence",\n "edge_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "identity": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "global-exact",\n "type": "string"\n },\n "strategy": {\n "const": "content-digest",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "original_source": {\n "type": "string"\n },\n "source_map_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "stability": {\n "const": "source-map-exact",\n "type": "string"\n },\n "strategy": {\n "const": "source-map-original",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "source_map_sha256",\n "original_source",\n "source_sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "canonical-path",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "minLength": 1,\n "type": "string"\n },\n "basis": {\n "items": {\n "enum": [\n "syntax-tree",\n "control-flow",\n "imports",\n "exports",\n "literals",\n "api-usage"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "fingerprint_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "stability": {\n "const": "cross-version-inference",\n "type": "string"\n },\n "strategy": {\n "const": "structural-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "algorithm",\n "fingerprint_sha256",\n "basis"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "key": {\n "type": "string"\n },\n "namespace": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "artifact-version",\n "type": "string"\n },\n "strategy": {\n "const": "artifact-local-key",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "artifact_sha256",\n "namespace",\n "key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_key": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "capture-only",\n "type": "string"\n },\n "strategy": {\n "const": "runtime-instance",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "capture_sha256",\n "runtime_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "observation_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope": {\n "minLength": 1,\n "type": "string"\n },\n "stability": {\n "const": "observation-only",\n "type": "string"\n },\n "strategy": {\n "const": "observation-fingerprint",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability",\n "observation_sha256",\n "scope"\n ],\n "type": "object"\n }\n ]\n },\n "kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "observations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "available",\n "artifact_id",\n "sha256"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "authority": {\n "enum": [\n "artifact-bytes",\n "ast-static-analysis",\n "static-relationship-inference",\n "passive-cdp-runtime",\n "cross-layer-reconciliation",\n "cross-version-comparison",\n "controlled-replay",\n "native-analysis-provider",\n "managed-static-analysis",\n "historical-reference",\n "user-assertion",\n "unknown"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low",\n "unknown"\n ],\n "type": "string"\n },\n "coverage": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "const": 0,\n "type": "number"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "omitted_count": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "partial",\n "type": "string"\n },\n "truncated": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "limits": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "unit": {\n "enum": [\n "items",\n "bytes",\n "milliseconds",\n "depth",\n "characters",\n "instructions",\n "other"\n ],\n "type": "string"\n },\n "value": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "name",\n "value",\n "unit"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "omitted_count": {\n "type": "null"\n },\n "status": {\n "enum": [\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "truncated": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "status",\n "truncated",\n "omitted_count",\n "limits"\n ],\n "type": "object"\n }\n ]\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "extractor": {\n "additionalProperties": false,\n "properties": {\n "executable_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "operation",\n "executable_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "location": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "value": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "kind": {\n "const": "source-range",\n "type": "string"\n },\n "source": {\n "minLength": 1,\n "type": "string"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "source",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "url",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "frame_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "kind": {\n "const": "runtime",\n "type": "string"\n },\n "script_key": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "target_key": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "capture_sha256",\n "target_key",\n "frame_key",\n "script_key"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "native-address",\n "type": "string"\n },\n "symbol": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "address",\n "symbol"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "export_path": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "package-export",\n "type": "string"\n },\n "package_name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "package_name",\n "export_path"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "available",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "enum": [\n "not-applicable",\n "not-observed",\n "truncated",\n "provider-unavailable",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "available",\n "reason",\n "detail"\n ],\n "type": "object"\n }\n ]\n },\n "state": {\n "enum": [\n "observed",\n "inferred",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "authority",\n "state",\n "confidence",\n "artifact",\n "location",\n "extractor",\n "coverage",\n "limitations",\n "evidence_ids"\n ],\n "type": "object"\n },\n "identifier_strategy": {\n "additionalProperties": false,\n "properties": {\n "stability": {\n "const": "observation-exact",\n "type": "string"\n },\n "strategy": {\n "const": "semantic-content-sha256",\n "type": "string"\n }\n },\n "required": [\n "strategy",\n "stability"\n ],\n "type": "object"\n },\n "label": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "observation_id": {\n "pattern": "^jag_observation_[a-f0-9]{64}$",\n "type": "string"\n },\n "properties": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "minLength": 1,\n "type": "string"\n },\n "type": "object"\n }\n },\n "required": [\n "label",\n "properties",\n "evidence",\n "observation_id",\n "identifier_strategy"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "node_id",\n "kind",\n "identity",\n "observations"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "root_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "schema": {\n "const": "JavaScriptApplicationGraph",\n "type": "string"\n }\n },\n "required": [\n "schema",\n "root_node_ids",\n "nodes",\n "edges",\n "coverage",\n "limitations",\n "graph_id"\n ],\n "type": "object"\n },\n "items": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "content",\n "location",\n "properties",\n "relationships",\n "availability",\n "coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^javc_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "left_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "exact-node-identity",\n "exact-content-digest",\n "exact-module-source-digest"\n ],\n "type": "string"\n },\n "candidate_left_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "exact",\n "type": "string"\n },\n "status": {\n "const": "matched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_node_ids",\n "candidate_right_node_ids"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "source-map-identity",\n "type": "string"\n },\n "candidate_left_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "high",\n "type": "string"\n },\n "status": {\n "const": "matched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_node_ids",\n "candidate_right_node_ids"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "structural-fingerprint",\n "semantic-key"\n ],\n "type": "string"\n },\n "candidate_left_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "medium",\n "type": "string"\n },\n "status": {\n "const": "matched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_node_ids",\n "candidate_right_node_ids"\n ],\n "type": "object"\n }\n ]\n },\n "node_kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "right_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "unchanged",\n "changed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "node_kind",\n "dimensions",\n "evidence_links",\n "limitations",\n "status",\n "left_node_id",\n "right_node_id",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "content",\n "location",\n "properties",\n "relationships",\n "availability",\n "coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^javc_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "left_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_node_ids",\n "candidate_right_node_ids"\n ],\n "type": "object"\n },\n "node_kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "right_node_id": {\n "type": "null"\n },\n "status": {\n "enum": [\n "removed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "node_kind",\n "dimensions",\n "evidence_links",\n "limitations",\n "status",\n "left_node_id",\n "right_node_id",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "content",\n "location",\n "properties",\n "relationships",\n "availability",\n "coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^javc_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "left_node_id": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "unmatched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_node_ids",\n "candidate_right_node_ids"\n ],\n "type": "object"\n },\n "node_kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "right_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "added",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "node_kind",\n "dimensions",\n "evidence_links",\n "limitations",\n "status",\n "left_node_id",\n "right_node_id",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "content",\n "location",\n "properties",\n "relationships",\n "availability",\n "coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^javc_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "left_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "candidate_right_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "prefixItems": [\n {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n ],\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "ambiguous",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_node_ids",\n "candidate_right_node_ids"\n ],\n "type": "object"\n },\n "node_kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "right_node_id": {\n "type": "null"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "node_kind",\n "dimensions",\n "evidence_links",\n "limitations",\n "status",\n "left_node_id",\n "right_node_id",\n "match"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimensions": {\n "items": {\n "enum": [\n "content",\n "location",\n "properties",\n "relationships",\n "availability",\n "coverage"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "item_id": {\n "pattern": "^javc_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "left_node_id": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "match": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "const": "none",\n "type": "string"\n },\n "candidate_left_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "prefixItems": [\n {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n ],\n "type": "array"\n },\n "candidate_right_node_ids": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "confidence": {\n "const": "unknown",\n "type": "string"\n },\n "status": {\n "const": "ambiguous",\n "type": "string"\n }\n },\n "required": [\n "status",\n "basis",\n "confidence",\n "candidate_left_node_ids",\n "candidate_right_node_ids"\n ],\n "type": "object"\n },\n "node_kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "right_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "item_id",\n "node_kind",\n "dimensions",\n "evidence_links",\n "limitations",\n "status",\n "left_node_id",\n "right_node_id",\n "match"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "left": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "root_artifact_sha256"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "matching": {\n "additionalProperties": false,\n "properties": {\n "ambiguous": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "exact_content_digest": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "exact_module_source_digest": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "exact_node_identity": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "semantic_key": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "source_map_identity": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "structural_fingerprint": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unmatched": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "exact_node_identity",\n "exact_content_digest",\n "exact_module_source_digest",\n "source_map_identity",\n "structural_fingerprint",\n "semantic_key",\n "ambiguous",\n "unmatched"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "root_artifact_sha256"\n ],\n "type": "object"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "added": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "changed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unchanged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "unknown"\n ],\n "type": "object"\n }\n },\n "required": [\n "comparison_id",\n "left",\n "right",\n "summary",\n "matching",\n "items",\n "graph",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Compare Application Versions"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare a cryptographically committed HistoricalSourceGraph with authenticated JavaScript Application Graph Evidence supplied inline or by exact same-session retained reference. Uses explicit exact-digest, source-map path, current-path, suffix, and basename signals with stable weights. Classifies unchanged, modified, removed, split, merged, duplicated, and unknown; incomplete coverage and ambiguous weak signals never become absence or forced matches.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "application": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "format": "directory",\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [],\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "limitations": [],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "inventory_graph_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "inventory_manifest_id": "agm_2222222222222222222222222222222222222222222222222222222222222222",\n "limitations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "semantic_graph": {\n "application_graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "coverage": {\n "families": [\n {\n "family": "boundary",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "call-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "child-process",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "closure",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "configuration",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "data-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "event",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "object-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "promise-ownership",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "request",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "resource-lifecycle",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "timer",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n }\n ],\n "limits": [],\n "omitted_nodes": 0,\n "omitted_relations": 0,\n "status": "unknown",\n "truncated": false\n },\n "fingerprints": [],\n "graph_id": "jsrg_08c925d2bf4ef56d770e0e67901d5bdfb610744cf001a50d36c9bb6dcb195508",\n "limitations": [\n "Semantic relations are unavailable in this minimal example."\n ],\n "nodes": [\n {\n "application_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "function_node_id": null,\n "identifier_strategy": {\n "stability": "artifact-version",\n "strategy": "semantic-content-sha256"\n },\n "identity": {\n "artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "module_path": "renderer.js",\n "role_key": "example-module",\n "source_range": null\n },\n "kind": "module",\n "label": "renderer.js",\n "node_id": "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a",\n "properties": {}\n }\n ],\n "relations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "root_node_ids": [\n "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a"\n ],\n "schema": "JavaScriptSemanticRelationGraph",\n "unknowns": []\n },\n "statistics": {\n "findings": 0,\n "invalid_utf8_files": 0,\n "modules": 0,\n "nested_asar_containers": 0,\n "parse_failures": 0,\n "parsed_javascript_files": 1,\n "relevant_files": 1,\n "text_bytes_read": 29,\n "truncated_scopes": 0,\n "visited_ast_nodes": 1\n },\n "summary": {\n "browser_windows": 0,\n "context_bridge_apis": 0,\n "explicit_web_preferences": 0,\n "exposed_api_members": 0,\n "ipc": {\n "ambiguous_renderer_transmissions": 0,\n "dynamic_channel_operations": 0,\n "literal_channels": 0,\n "main_handlers": 0,\n "operations": 0,\n "paired_renderer_transmissions": 0,\n "renderer_listeners": 0,\n "renderer_transmissions": 0,\n "unpaired_literal_renderer_transmissions": 0\n },\n "native_addon_bindings": 0,\n "preload_entrypoints": 0,\n "resolved_native_addon_bindings": 0,\n "resolved_utility_entrypoints": 0,\n "sender_validation_observations": 0,\n "utility_processes": 0\n }\n },\n "operation": "analyze_javascript_application",\n "parameters": {},\n "predicate_type": "rea.javascript-application-analysis",\n "provider": {\n "id": "rea-javascript-application",\n "name": "REA JavaScript application analyzer",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "directory",\n "local_path": "/Applications/Example.app/Contents/Resources/app",\n "name": "app"\n }\n },\n "reference": {\n "authority": "historical-reference",\n "entries": [\n {\n "classifications": [\n "source"\n ],\n "kind": "directory",\n "limitations": [],\n "path": "src",\n "tree_state": "enumerated"\n },\n {\n "classifications": [\n "source"\n ],\n "content_state": "hashed",\n "kind": "file",\n "language": "TypeScript",\n "limitations": [],\n "path": "src/main.ts",\n "sha256": "2222222222222222222222222222222222222222222222222222222222222222",\n "size": 128\n }\n ],\n "exclusions": [],\n "inventory_state": "complete",\n "languages": [\n "TypeScript"\n ],\n "limitations": [],\n "manifests": [],\n "parse_failures": [],\n "provenance": {\n "caller": "contract-example",\n "importer": "rea",\n "importer_version": "1"\n },\n "relationships": [],\n "root_alias": "$REFERENCE_ROOT",\n "root_sha256": "683969414c3e674a368392213c78021322fb39609b0c57f346b22da6464b2110",\n "schema": "HistoricalSourceGraph",\n "vcs": {\n "dirty": null,\n "head": null,\n "kind": "none"\n }\n }\n }\n ],\n "properties": {\n "application": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "retained-evidence",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id"\n ],\n "type": "object"\n }\n ],\n "description": "Value for application."\n },\n "reference": {\n "additionalProperties": false,\n "description": "Value for reference.",\n "properties": {\n "authority": {\n "const": "historical-reference",\n "description": "Value for authority.",\n "type": "string"\n },\n "entries": {\n "description": "Value for entries.",\n "items": {\n "anyOf": [\n {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "classifications": {\n "description": "Value for classifications.",\n "items": {\n "enum": [\n "source",\n "test",\n "config",\n "manifest",\n "generated",\n "vendor",\n "documentation",\n "unknown"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "content_state": {\n "const": "hashed",\n "description": "Value for content state.",\n "type": "string"\n },\n "kind": {\n "const": "file",\n "description": "Value for kind.",\n "type": "string"\n },\n "language": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for language."\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "size": {\n "description": "Value for size.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "path",\n "classifications",\n "limitations",\n "kind",\n "language",\n "sha256",\n "size",\n "content_state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "classifications": {\n "description": "Value for classifications.",\n "items": {\n "enum": [\n "source",\n "test",\n "config",\n "manifest",\n "generated",\n "vendor",\n "documentation",\n "unknown"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "content_state": {\n "description": "Value for content state.",\n "enum": [\n "redacted-secret",\n "excluded",\n "unreadable",\n "unknown"\n ],\n "type": "string"\n },\n "kind": {\n "const": "file",\n "description": "Value for kind.",\n "type": "string"\n },\n "language": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for language."\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "description": "Value for sha256.",\n "type": "null"\n },\n "size": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for size."\n }\n },\n "required": [\n "path",\n "classifications",\n "limitations",\n "kind",\n "language",\n "sha256",\n "size",\n "content_state"\n ],\n "type": "object"\n }\n ]\n },\n {\n "additionalProperties": false,\n "properties": {\n "classifications": {\n "description": "Value for classifications.",\n "items": {\n "enum": [\n "source",\n "test",\n "config",\n "manifest",\n "generated",\n "vendor",\n "documentation",\n "unknown"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "kind": {\n "const": "directory",\n "description": "Value for kind.",\n "type": "string"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "tree_state": {\n "description": "Value for tree state.",\n "enum": [\n "enumerated",\n "partial",\n "excluded",\n "unreadable",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "classifications",\n "limitations",\n "kind",\n "tree_state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "classifications": {\n "description": "Value for classifications.",\n "items": {\n "enum": [\n "source",\n "test",\n "config",\n "manifest",\n "generated",\n "vendor",\n "documentation",\n "unknown"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "kind": {\n "const": "symlink",\n "description": "Value for kind.",\n "type": "string"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "target": {\n "description": "Value for target.",\n "minLength": 1,\n "type": "string"\n },\n "target_state": {\n "description": "Value for target state.",\n "enum": [\n "internal",\n "external",\n "missing",\n "unreadable",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "classifications",\n "limitations",\n "kind",\n "target",\n "target_state"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "exclusions": {\n "description": "Value for exclusions.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "description": "Value for reason.",\n "enum": [\n "configured-secret",\n "symlink-escape",\n "size-limit",\n "inventory-limit",\n "unreadable",\n "caller-excluded"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "inventory_state": {\n "description": "Value for inventory state.",\n "enum": [\n "complete",\n "partial",\n "unknown"\n ],\n "type": "string"\n },\n "languages": {\n "description": "Value for languages.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "manifests": {\n "description": "Value for manifests.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "parse_failures": {\n "description": "Value for parse failures.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "parser": {\n "description": "Value for parser.",\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "description": "Value for reason.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "parser",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "provenance": {\n "additionalProperties": false,\n "description": "Value for provenance.",\n "properties": {\n "caller": {\n "description": "Value for caller.",\n "minLength": 1,\n "pattern": "^[\\\\w .:@/+-]+$",\n "type": "string"\n },\n "importer": {\n "description": "Value for importer.",\n "minLength": 1,\n "type": "string"\n },\n "importer_version": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for importer version."\n }\n },\n "required": [\n "importer",\n "importer_version",\n "caller"\n ],\n "type": "object"\n },\n "relationships": {\n "description": "Value for relationships.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "from_path": {\n "description": "Local filesystem path for from.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "imports",\n "requires",\n "references",\n "declares-module"\n ],\n "type": "string"\n },\n "parse_state": {\n "description": "Value for parse state.",\n "enum": [\n "parsed",\n "partial",\n "unknown"\n ],\n "type": "string"\n },\n "resolution": {\n "description": "Value for resolution.",\n "enum": [\n "internal",\n "external",\n "unresolved",\n "unknown"\n ],\n "type": "string"\n },\n "to": {\n "description": "Value for to.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "from_path",\n "to",\n "kind",\n "resolution",\n "parse_state"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "root_alias": {\n "const": "$REFERENCE_ROOT",\n "description": "Value for root alias.",\n "type": "string"\n },\n "root_sha256": {\n "description": "Exact SHA-256 digest of root.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "schema": {\n "const": "HistoricalSourceGraph",\n "description": "Value for schema.",\n "type": "string"\n },\n "vcs": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "dirty": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for dirty."\n },\n "head": {\n "description": "Value for head.",\n "pattern": "^(?:[a-f0-9]{40}|[a-f0-9]{64})$",\n "type": "string"\n },\n "kind": {\n "const": "git",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "head",\n "dirty"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dirty": {\n "description": "Value for dirty.",\n "type": "null"\n },\n "head": {\n "description": "Value for head.",\n "type": "null"\n },\n "kind": {\n "const": "none",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "head",\n "dirty"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dirty": {\n "description": "Value for dirty.",\n "type": "null"\n },\n "head": {\n "description": "Value for head.",\n "type": "null"\n },\n "kind": {\n "const": "unknown",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "head",\n "dirty"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for vcs."\n }\n },\n "required": [\n "schema",\n "authority",\n "root_alias",\n "root_sha256",\n "inventory_state",\n "entries",\n "relationships",\n "parse_failures",\n "exclusions",\n "languages",\n "manifests",\n "vcs",\n "provenance",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "reference",\n "application"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "compare_source_to_bundle",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "application": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_artifact_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "root_artifact_sha256"\n ],\n "type": "object"\n },\n "comparison_id": {\n "pattern": "^stbc_[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "application_graph_status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "candidate_evaluations": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "reference_inventory_state": {\n "enum": [\n "complete",\n "partial",\n "unknown"\n ],\n "type": "string"\n },\n "retained_application_nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "retained_source_files": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "status": {\n "enum": [\n "complete-within-inputs",\n "partial"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "reference_inventory_state",\n "application_graph_status",\n "retained_source_files",\n "retained_application_nodes",\n "candidate_evaluations"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 1,\n "minItems": 1,\n "type": "array"\n },\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "low"\n ],\n "type": "string"\n },\n "current_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "current_node_kind": {\n "enum": [\n "package",\n "installer",\n "artifact",\n "asar-entry",\n "electron-main",\n "electron-preload",\n "electron-renderer",\n "electron-utility",\n "javascript-asset",\n "javascript-chunk",\n "javascript-module",\n "source-map",\n "source-module",\n "browser-window",\n "frame",\n "target",\n "context-bridge-api",\n "ipc-channel",\n "ipc-handler",\n "worker",\n "service-worker",\n "endpoint",\n "storage",\n "native-addon",\n "native-export",\n "managed-assembly",\n "managed-module",\n "managed-type",\n "managed-method",\n "managed-field",\n "managed-pinvoke-import",\n "managed-native-implementation",\n "runtime-script-instance",\n "unknown"\n ],\n "type": "string"\n },\n "score": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "signals": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "current_values": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "kind": {\n "enum": [\n "exact-source-digest",\n "source-map-original-path",\n "current-path-exact",\n "current-path-suffix",\n "basename-match",\n "language-extension"\n ],\n "type": "string"\n },\n "source_value": {\n "minLength": 1,\n "type": "string"\n },\n "weight": {\n "maximum": 100,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "weight",\n "source_value",\n "current_values"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "current_node_id",\n "current_node_kind",\n "score",\n "confidence",\n "signals"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "confidence": {\n "enum": [\n "exact",\n "high",\n "medium",\n "unknown"\n ],\n "type": "string"\n },\n "current_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "mapping_id": {\n "pattern": "^stbc_item_[a-f0-9]{64}$",\n "type": "string"\n },\n "source_language": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "source_path": {\n "minLength": 1,\n "type": "string"\n },\n "source_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "unchanged",\n "modified",\n "removed",\n "split",\n "merged",\n "duplicated",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "mapping_id",\n "source_path",\n "source_sha256",\n "source_language",\n "status",\n "confidence",\n "current_node_ids",\n "candidates",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "reference": {\n "additionalProperties": false,\n "properties": {\n "inventory_state": {\n "enum": [\n "complete",\n "partial",\n "unknown"\n ],\n "type": "string"\n },\n "root_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "root_sha256",\n "inventory_state"\n ],\n "type": "object"\n },\n "scoring": {\n "additionalProperties": false,\n "properties": {\n "algorithm": {\n "const": "rea-source-to-bundle-signals",\n "type": "string"\n },\n "minimum_candidate_score": {\n "const": 20,\n "type": "number"\n },\n "weights": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "signal": {\n "enum": [\n "exact-source-digest",\n "source-map-original-path",\n "current-path-exact",\n "current-path-suffix",\n "basename-match",\n "language-extension"\n ],\n "type": "string"\n },\n "weight": {\n "maximum": 100,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "signal",\n "weight"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "algorithm",\n "minimum_candidate_score",\n "weights"\n ],\n "type": "object"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "duplicated": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "merged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "modified": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "split": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unchanged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unchanged",\n "modified",\n "removed",\n "split",\n "merged",\n "duplicated",\n "unknown"\n ],\n "type": "object"\n },\n "unmapped_current_node_ids": {\n "items": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "comparison_id",\n "reference",\n "application",\n "scoring",\n "summary",\n "items",\n "unmapped_current_node_ids",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Compare Source To Bundle"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare static return shapes for one exact module/export selector on each authenticated JavaScript Application Graph supplied as inline Evidence or exact same-session retained references. Variants pair only by reciprocal unique literal discriminants; dynamic values, incomplete properties, and ambiguous variants remain unknown. Reports JSON Pointer changes without executing JavaScript; runtime behavior requires a separate agent-run probe.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "left": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "format": "directory",\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [],\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "limitations": [],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "inventory_graph_sha256": "3333333333333333333333333333333333333333333333333333333333333333",\n "inventory_manifest_id": "agm_2222222222222222222222222222222222222222222222222222222222222222",\n "limitations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "semantic_graph": {\n "application_graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "coverage": {\n "families": [\n {\n "family": "boundary",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "call-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "child-process",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "closure",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "configuration",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "data-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "event",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "object-flow",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "promise-ownership",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "request",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "resource-lifecycle",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n },\n {\n "family": "timer",\n "omitted_relations": 0,\n "retained_relations": 0,\n "status": "unknown",\n "unknown_ids": []\n }\n ],\n "limits": [],\n "omitted_nodes": 0,\n "omitted_relations": 0,\n "status": "unknown",\n "truncated": false\n },\n "fingerprints": [],\n "graph_id": "jsrg_08c925d2bf4ef56d770e0e67901d5bdfb610744cf001a50d36c9bb6dcb195508",\n "limitations": [\n "Semantic relations are unavailable in this minimal example."\n ],\n "nodes": [\n {\n "application_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n ],\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "function_node_id": null,\n "identifier_strategy": {\n "stability": "artifact-version",\n "strategy": "semantic-content-sha256"\n },\n "identity": {\n "artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "module_path": "renderer.js",\n "role_key": "example-module",\n "source_range": null\n },\n "kind": "module",\n "label": "renderer.js",\n "node_id": "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a",\n "properties": {}\n }\n ],\n "relations": [],\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "root_node_ids": [\n "jsrg_node_9476602f1f74433a2bd3dadb040a25734d203b8f831b31a8d96f7efdfc5cd66a"\n ],\n "schema": "JavaScriptSemanticRelationGraph",\n "unknowns": []\n },\n "statistics": {\n "findings": 0,\n "invalid_utf8_files": 0,\n "modules": 0,\n "nested_asar_containers": 0,\n "parse_failures": 0,\n "parsed_javascript_files": 1,\n "relevant_files": 1,\n "text_bytes_read": 29,\n "truncated_scopes": 0,\n "visited_ast_nodes": 1\n },\n "summary": {\n "browser_windows": 0,\n "context_bridge_apis": 0,\n "explicit_web_preferences": 0,\n "exposed_api_members": 0,\n "ipc": {\n "ambiguous_renderer_transmissions": 0,\n "dynamic_channel_operations": 0,\n "literal_channels": 0,\n "main_handlers": 0,\n "operations": 0,\n "paired_renderer_transmissions": 0,\n "renderer_listeners": 0,\n "renderer_transmissions": 0,\n "unpaired_literal_renderer_transmissions": 0\n },\n "native_addon_bindings": 0,\n "preload_entrypoints": 0,\n "resolved_native_addon_bindings": 0,\n "resolved_utility_entrypoints": 0,\n "sender_validation_observations": 0,\n "utility_processes": 0\n }\n },\n "operation": "analyze_javascript_application",\n "parameters": {},\n "predicate_type": "rea.javascript-application-analysis",\n "provider": {\n "id": "rea-javascript-application",\n "name": "REA JavaScript application analyzer",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "directory",\n "local_path": "/Applications/Example.app/Contents/Resources/app",\n "name": "app"\n }\n },\n "left_export_name": "default",\n "left_module_path": "parser.mjs",\n "right": {\n "authority": "analyst-inference",\n "confidence": "inferred",\n "environment": null,\n "evidence_id": "ev_c2754cf75e286b839ed825f247062067a6472acb1e7b6ce0f9aaceec6448d6d7",\n "evidence_links": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54"\n ],\n "limitations": [\n "Artifact identity is unavailable for this observation.",\n "A static module resident in an observed bundle is not reported as executed.",\n "Caller-declared file and URL mappings are inference inputs and do not expand CDP origin or filesystem-root authority.",\n "Digest equality establishes byte identity but not code reachability, module initialization, or feature execution.",\n "Not-observed means absent from the bounded captures in scope, never globally unloaded.",\n "Only caller-supplied, semantically verified Evidence records were reconciled.",\n "Source-map authority remains separate from passive runtime authority."\n ],\n "locations": [],\n "normalized_result": {\n "coverage": {\n "omitted_graph_items": 0,\n "omitted_reconciliation_items": 0,\n "omitted_runtime_entities": 0,\n "omitted_static_load_states": 0,\n "status": "complete-within-inputs",\n "truncated": false\n },\n "evidence_links": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54"\n ],\n "graph": {\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "edges": [\n {\n "edge_id": "jag_edge_3799eb1c2b0f224013a0edba8911a8b9edabd5fa6da2f893a6cfa24a899c9463",\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "cross-layer-reconciliation",\n "confidence": "high",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-runtime-reconciliation",\n "operation": "reconcile_javascript_runtime",\n "version": "1"\n },\n "limitations": [\n "Equal content digests establish byte identity, not execution causality.",\n "Passive script parsing or target presence does not prove feature execution."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "target_key": "example-target"\n }\n },\n "state": "inferred"\n },\n "identifier_strategy": {\n "stability": "relationship-exact",\n "strategy": "semantic-content-sha256"\n },\n "properties": {\n "basis": "content-and-location",\n "reconciliation_id": "jrr_item_ea9d751c4bf3a2234af56b80d5ac8a1f953da9a9477cd46586a62934ff4a5fb2",\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753"\n },\n "relation": "observed_as",\n "source_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "target_node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e"\n },\n {\n "edge_id": "jag_edge_f3c06b02c5ac77056b5d2a9f8de834ea0a9123b01dc369a011e3adc4a535338b",\n "evidence": {\n "artifact": {\n "available": false,\n "detail": "The relationship is runtime metadata, not standalone bytes.",\n "reason": "not-applicable"\n },\n "authority": "passive-cdp-runtime",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-cdp-electron",\n "operation": "inspect_electron_page",\n "version": "1"\n },\n "limitations": [\n "The relationship was observed only within one bounded passive CDP capture."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "target_key": "example-target"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "relationship-exact",\n "strategy": "semantic-content-sha256"\n },\n "properties": {\n "basis": "passive-cdp-observation"\n },\n "relation": "loads",\n "source_node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "target_node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e"\n }\n ],\n "graph_id": "jag_0536a9584eb91a16f78f71e8bef4cffc3105df18098fe65a7b114add520203ca",\n "limitations": [\n "Cross-layer observed_as edges are inferences; digest equality proves byte identity only.",\n "Runtime presence is bounded to passive capture windows and does not prove feature execution.",\n "Source-map relationships retain their original static authority and are not promoted into runtime matches."\n ],\n "nodes": [\n {\n "identity": {\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "stability": "global-exact",\n "strategy": "content-digest"\n },\n "kind": "javascript-asset",\n "node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "artifact-bytes",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-javascript-application",\n "operation": "inventory-relevant-file",\n "version": "1"\n },\n "limitations": [],\n "location": {\n "available": true,\n "value": {\n "kind": "artifact-path",\n "path": "renderer.js"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "renderer.js",\n "observation_id": "jag_observation_20b4d076ce370db14ae2b477915624c62e0c4a0c9e56803a2c5cb4fa60c16230",\n "properties": {\n "bytes": 29,\n "path": "renderer.js"\n }\n }\n ]\n },\n {\n "identity": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "runtime_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "stability": "capture-only",\n "strategy": "runtime-instance"\n },\n "kind": "runtime-script-instance",\n "node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "artifact_id": "art_c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64",\n "available": true,\n "sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n },\n "authority": "passive-cdp-runtime",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-cdp-electron",\n "operation": "inspect_electron_page",\n "version": "1"\n },\n "limitations": [\n "The entity was observed only within one bounded passive CDP capture."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": "electron_script_4444444444444444444444444444444444444444444444444444444444444444",\n "target_key": "example-target"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "/Applications/Example.app/Contents/Resources/app/renderer.js",\n "observation_id": "jag_observation_9315403b875ea70c310909daf797780ab9045ff8f4803db38a9184e4f0704f11",\n "properties": {\n "cdp_hash": "example",\n "is_module": true,\n "language": "JavaScript",\n "length": 29,\n "location": "/Applications/Example.app/Contents/Resources/app/renderer.js",\n "source_sha256": "c4c0d760a4c84f0e182de99f6ef89ef10831f8170e486c4d4bd934924151ba64"\n }\n }\n ]\n },\n {\n "identity": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "runtime_key": "example-target",\n "stability": "capture-only",\n "strategy": "runtime-instance"\n },\n "kind": "target",\n "node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "observations": [\n {\n "evidence": {\n "artifact": {\n "available": false,\n "detail": "This runtime entity has no standalone artifact bytes.",\n "reason": "not-applicable"\n },\n "authority": "passive-cdp-runtime",\n "confidence": "exact",\n "coverage": {\n "limits": [],\n "omitted_count": 0,\n "status": "complete",\n "truncated": false\n },\n "evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "extractor": {\n "executable_sha256": null,\n "name": "rea-cdp-electron",\n "operation": "inspect_electron_page",\n "version": "1"\n },\n "limitations": [\n "The entity was observed only within one bounded passive CDP capture."\n ],\n "location": {\n "available": true,\n "value": {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "frame_key": null,\n "kind": "runtime",\n "script_key": null,\n "target_key": "example-target"\n }\n },\n "state": "observed"\n },\n "identifier_strategy": {\n "stability": "observation-exact",\n "strategy": "semantic-content-sha256"\n },\n "label": "Example",\n "observation_id": "jag_observation_844a03a29af96a0bcd4625e0377b67c439af68a462c5fa83009c7c27283c5dee",\n "properties": {\n "attached": false,\n "location": "/Applications/Example.app/Contents/Resources/app/index.html",\n "runtime_type": "page"\n }\n }\n ]\n }\n ],\n "root_node_ids": [\n "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265"\n ],\n "schema": "JavaScriptApplicationGraph"\n },\n "limitations": [\n "A static module resident in an observed bundle is not reported as executed.",\n "Caller-declared file and URL mappings are inference inputs and do not expand CDP origin or filesystem-root authority.",\n "Digest equality establishes byte identity but not code reachability, module initialization, or feature execution.",\n "Not-observed means absent from the bounded captures in scope, never globally unloaded.",\n "Only caller-supplied, semantically verified Evidence records were reconciled.",\n "Source-map authority remains separate from passive runtime authority."\n ],\n "reconciliation_id": "jrr_ec50425a5a5a5cb85aa40f13f72dcba54a8ab16170d9421a0277fbdf0fa4c9eb",\n "reconciliations": [\n {\n "basis": "none",\n "candidate_static_count": 0,\n "candidate_static_nodes": [],\n "confidence": "unknown",\n "entity_kind": "target",\n "reason": "no-static-content-match",\n "reconciliation_id": "jrr_item_abd5635b06340f082ef46bff8802a361c7b30011085f1c9e7437b29b451c1e5e",\n "runtime_evidence_id": "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "runtime_node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "static_layer_id": null,\n "static_node_id": null,\n "status": "unmatched"\n },\n {\n "basis": "content-and-location",\n "candidate_static_count": 1,\n "candidate_static_nodes": [\n {\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "static_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e"\n }\n ],\n "confidence": "high",\n "entity_kind": "script",\n "reason": "content-and-location-match",\n "reconciliation_id": "jrr_item_ea9d751c4bf3a2234af56b80d5ac8a1f953da9a9477cd46586a62934ff4a5fb2",\n "runtime_evidence_id": "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "runtime_node_id": "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e",\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "static_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "status": "matched"\n }\n ],\n "runtime_captures": [\n {\n "capture_sha256": "a4dd260f01b45e3df92482755baed7a1922b4af2226edc7f896bdbc4d2bbbd9c",\n "evidence_id": "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e",\n "frames": 0,\n "kind": "electron",\n "scripts": 1,\n "scripts_complete_within_scope": true,\n "target_key": "example-target",\n "target_location": "/Applications/Example.app/Contents/Resources/app/index.html",\n "target_node_id": "jag_node_f360c35c86d4893fcf83ef0fdef92cebd688fba883aa39698a1c12dd5a237265",\n "workers": 0\n }\n ],\n "source_map_authority": {\n "limitation": "Source-map declarations and original-source content retain separate static authority and are not used as primary runtime byte matches.",\n "runtime_script_declarations": 0,\n "static_layer_count": 1,\n "used_for_primary_matching": false\n },\n "static_layers": [\n {\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "format": "directory",\n "graph_id": "jag_ab15669664efc0a86bb49c9c60241913cce6980dc2293fbeef6d9c862ca01d9d",\n "input_path": "/Applications/Example.app/Contents/Resources/app",\n "layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "role": "application",\n "root_artifact_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "runtime_mappings": []\n }\n ],\n "static_load_states": [\n {\n "kind": "javascript-asset",\n "reason": "runtime-script-correspondence",\n "runtime_node_ids": [\n "jag_node_af8ea5d1909955f39da1d844b2cad8f2f68c34b7d575ded6d04c00fa4095a88e"\n ],\n "static_layer_id": "jrl_96fcfc590e7ca88f94b602638421674d8b13ccc7d25efff15a15b03c33b34753",\n "static_node_id": "jag_node_3c82da5492d90eb5b0bee41c1a994d88b7ba98c0b4955270d70e57a76e224c2e",\n "status": "loaded"\n }\n ],\n "summary": {\n "ambiguous": 0,\n "matched": 1,\n "runtime_frames": 0,\n "runtime_scripts": 1,\n "runtime_targets": 1,\n "runtime_workers": 0,\n "static_loaded": 1,\n "static_not_observed": 0,\n "static_resident": 0,\n "static_unknown": 0,\n "unknown": 0,\n "unmatched": 1\n }\n },\n "operation": "reconcile_javascript_runtime",\n "parameters": {\n "runtime_evidence_ids": [\n "ev_a42606ee799a093832ad3d6cfd0edd50dc9b99f78cc6d5d9130b734d48a56b4e"\n ],\n "static_layers": [\n {\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "role": "application",\n "runtime_mappings": []\n }\n ]\n },\n "predicate_type": "rea.javascript-runtime-reconciliation",\n "provider": {\n "id": "rea-javascript-runtime-reconciliation",\n "name": "REA JavaScript runtime reconciliation",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n },\n "right_export_name": "default",\n "right_module_path": "parser.mjs"\n },\n {\n "left": {\n "evidence_id": "ev_a4ae2c7b5c501fb6dff695b168f35a6dd157fa957410910684f3a4ce4c8b5d54",\n "kind": "retained-evidence"\n },\n "left_export_name": "default",\n "left_module_path": "parser.mjs",\n "right": {\n "evidence_id": "ev_c2754cf75e286b839ed825f247062067a6472acb1e7b6ce0f9aaceec6448d6d7",\n "kind": "retained-evidence"\n },\n "right_export_name": "default",\n "right_module_path": "parser.mjs"\n }\n ],\n "properties": {\n "left": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "retained-evidence",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id"\n ],\n "type": "object"\n }\n ],\n "description": "Left-hand input used for comparison or differential execution."\n },\n "left_export_name": {\n "description": "Value for left export name.",\n "minLength": 1,\n "type": "string"\n },\n "left_module_path": {\n "description": "Local filesystem path for left module.",\n "minLength": 1,\n "type": "string"\n },\n "right": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "const": "retained-evidence",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id"\n ],\n "type": "object"\n }\n ],\n "description": "Right-hand input used for comparison or differential execution."\n },\n "right_export_name": {\n "description": "Value for right export name.",\n "minLength": 1,\n "type": "string"\n },\n "right_module_path": {\n "description": "Local filesystem path for right module.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "left_module_path",\n "left_export_name",\n "right_module_path",\n "right_export_name",\n "left",\n "right"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "compare_javascript_export_shapes",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "change_id": {\n "pattern": "^jesc_change_[a-f0-9]{64}$",\n "type": "string"\n },\n "discriminant": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "path": {\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "value"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "left": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "absent",\n "type": "string"\n }\n },\n "required": [\n "availability"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "literal",\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "availability",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "union",\n "type": "string"\n },\n "values": {\n "items": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "availability",\n "values"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "unknown",\n "type": "string"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "availability",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "left_source_range": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "path": {\n "type": "string"\n },\n "right": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "absent",\n "type": "string"\n }\n },\n "required": [\n "availability"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "literal",\n "type": "string"\n },\n "value": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "availability",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "union",\n "type": "string"\n },\n "values": {\n "items": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "availability",\n "values"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "const": "unknown",\n "type": "string"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "availability",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "right_source_range": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "added",\n "removed",\n "changed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "change_id",\n "status",\n "path",\n "discriminant",\n "left",\n "right",\n "left_source_range",\n "right_source_range",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "comparison_id": {\n "pattern": "^jesc_[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "left_graph_status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "left_omitted_fields": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "left_omitted_property_coverage": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "left_source_omitted_variants": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "omitted_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "omitted_changes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "omitted_left_variants": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "omitted_right_variants": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "paired_variants": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "right_graph_status": {\n "enum": [\n "complete",\n "partial",\n "unknown",\n "unavailable"\n ],\n "type": "string"\n },\n "right_omitted_fields": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "right_omitted_property_coverage": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "right_source_omitted_variants": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "enum": [\n "complete-within-inputs",\n "partial",\n "truncated"\n ],\n "type": "string"\n },\n "unpaired_left_variants": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unpaired_right_variants": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "status",\n "left_graph_status",\n "right_graph_status",\n "paired_variants",\n "unpaired_left_variants",\n "unpaired_right_variants",\n "omitted_left_variants",\n "omitted_right_variants",\n "left_source_omitted_variants",\n "right_source_omitted_variants",\n "left_omitted_fields",\n "right_omitted_fields",\n "left_omitted_property_coverage",\n "right_omitted_property_coverage",\n "omitted_candidates",\n "omitted_changes"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "maxItems": 2,\n "minItems": 2,\n "type": "array"\n },\n "left": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "export_name": {\n "minLength": 1,\n "type": "string"\n },\n "matches_requested_export": {\n "type": "boolean"\n },\n "matches_requested_module": {\n "type": "boolean"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "module_path",\n "export_name",\n "matches_requested_module",\n "matches_requested_export"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "omitted_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "requested_export_name": {\n "minLength": 1,\n "type": "string"\n },\n "requested_module_path": {\n "minLength": 1,\n "type": "string"\n },\n "selected_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "selected",\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "requested_module_path",\n "requested_export_name",\n "candidates",\n "omitted_candidates",\n "status",\n "selected_node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "export_name": {\n "minLength": 1,\n "type": "string"\n },\n "matches_requested_export": {\n "type": "boolean"\n },\n "matches_requested_module": {\n "type": "boolean"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "module_path",\n "export_name",\n "matches_requested_module",\n "matches_requested_export"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "omitted_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "requested_export_name": {\n "minLength": 1,\n "type": "string"\n },\n "requested_module_path": {\n "minLength": 1,\n "type": "string"\n },\n "selected_node_id": {\n "type": "null"\n },\n "status": {\n "enum": [\n "missing",\n "ambiguous"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "requested_module_path",\n "requested_export_name",\n "candidates",\n "omitted_candidates",\n "status",\n "selected_node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "export_name": {\n "minLength": 1,\n "type": "string"\n },\n "matches_requested_export": {\n "type": "boolean"\n },\n "matches_requested_module": {\n "type": "boolean"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "module_path",\n "export_name",\n "matches_requested_module",\n "matches_requested_export"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "omitted_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "requested_export_name": {\n "minLength": 1,\n "type": "string"\n },\n "requested_module_path": {\n "minLength": 1,\n "type": "string"\n },\n "selected_node_id": {\n "anyOf": [\n {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "requested_module_path",\n "requested_export_name",\n "candidates",\n "omitted_candidates",\n "status",\n "selected_node_id"\n ],\n "type": "object"\n }\n ]\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "right": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "export_name": {\n "minLength": 1,\n "type": "string"\n },\n "matches_requested_export": {\n "type": "boolean"\n },\n "matches_requested_module": {\n "type": "boolean"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "module_path",\n "export_name",\n "matches_requested_module",\n "matches_requested_export"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "omitted_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "requested_export_name": {\n "minLength": 1,\n "type": "string"\n },\n "requested_module_path": {\n "minLength": 1,\n "type": "string"\n },\n "selected_node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "selected",\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "requested_module_path",\n "requested_export_name",\n "candidates",\n "omitted_candidates",\n "status",\n "selected_node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "export_name": {\n "minLength": 1,\n "type": "string"\n },\n "matches_requested_export": {\n "type": "boolean"\n },\n "matches_requested_module": {\n "type": "boolean"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "module_path",\n "export_name",\n "matches_requested_module",\n "matches_requested_export"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "omitted_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "requested_export_name": {\n "minLength": 1,\n "type": "string"\n },\n "requested_module_path": {\n "minLength": 1,\n "type": "string"\n },\n "selected_node_id": {\n "type": "null"\n },\n "status": {\n "enum": [\n "missing",\n "ambiguous"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "requested_module_path",\n "requested_export_name",\n "candidates",\n "omitted_candidates",\n "status",\n "selected_node_id"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "export_name": {\n "minLength": 1,\n "type": "string"\n },\n "matches_requested_export": {\n "type": "boolean"\n },\n "matches_requested_module": {\n "type": "boolean"\n },\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "node_id": {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "node_id",\n "module_path",\n "export_name",\n "matches_requested_module",\n "matches_requested_export"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "graph_id": {\n "pattern": "^jag_[a-f0-9]{64}$",\n "type": "string"\n },\n "omitted_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "requested_export_name": {\n "minLength": 1,\n "type": "string"\n },\n "requested_module_path": {\n "minLength": 1,\n "type": "string"\n },\n "selected_node_id": {\n "anyOf": [\n {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "graph_id",\n "requested_module_path",\n "requested_export_name",\n "candidates",\n "omitted_candidates",\n "status",\n "selected_node_id"\n ],\n "type": "object"\n }\n ]\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "added": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "changed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "added",\n "removed",\n "changed",\n "unknown"\n ],\n "type": "object"\n }\n },\n "required": [\n "comparison_id",\n "left",\n "right",\n "summary",\n "changes",\n "coverage",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Compare Javascript Export Shapes"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Generate the complete deterministic ReconstructionObligationLedger from an authenticated Evidence bundle, reviewed obligations, and an explicit reconstruction manifest. Static candidates remain candidates; duplicate ownership, missing original or reconstruction cases, missing parser/type, weak verifier authority, unenumerated claims, contradictions, dependencies, and residual unknowns fail closed. The inline result carries the full-ledger closure digest and typed per-obligation diagnostics.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "evidence_bundle": {\n "artifacts": [],\n "captures": [],\n "environments": [],\n "providers": [],\n "records": [],\n "scenarios": [],\n "unknowns": []\n },\n "manifest": {\n "bindings": [],\n "contradictions": []\n },\n "reviewed_obligations": []\n }\n ],\n "properties": {\n "evidence_bundle": {\n "description": "Value for evidence bundle.",\n "properties": {\n "artifacts": {\n "description": "Value for artifacts.",\n "items": {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n }\n },\n "required": [\n "digest",\n "format",\n "architecture"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "captures": {\n "description": "Value for captures.",\n "items": {\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "predicate_type"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "environments": {\n "description": "Value for environments.",\n "items": {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "providers": {\n "description": "Value for providers.",\n "items": {\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "records": {\n "description": "Value for records.",\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links",\n "analysis_profile"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "scenarios": {\n "description": "Value for scenarios.",\n "items": {\n "properties": {\n "authority": {\n "const": "controlled-replay",\n "description": "Value for authority.",\n "type": "string"\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "authority"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "unknowns": {\n "description": "Value for unknowns.",\n "items": {\n "properties": {\n "contradicting_evidence_ids": {\n "description": "Ordered Evidence identifiers for the contradicting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "description": "Value for domain.",\n "minLength": 1,\n "type": "string"\n },\n "mutation_evidence_ids": {\n "description": "Ordered Evidence identifiers for the mutation observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "previous_revision_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for previous revision digest."\n },\n "question": {\n "description": "Concrete unresolved question to retain for later investigation.",\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "description": "Value for recommended probes.",\n "items": {\n "properties": {\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "description": "Value for relationships.",\n "items": {\n "properties": {\n "type": {\n "description": "Value for type.",\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required authority."\n },\n "required_confidence": {\n "description": "Value for required confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for id."\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for isolation."\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for platform."\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required environment."\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "properties": {\n "disposition": {\n "const": "verified",\n "description": "Value for disposition.",\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "properties": {\n "disposition": {\n "description": "Value for disposition.",\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for resolution."\n },\n "revision": {\n "description": "Value for revision.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "revision_digest": {\n "description": "Value for revision digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for scope digest."\n },\n "severity": {\n "description": "Value for severity.",\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "description": "Ordered Evidence identifiers for the supporting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "revision",\n "previous_revision_digest",\n "revision_digest",\n "scope_digest",\n "question",\n "status",\n "severity",\n "domain",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution",\n "mutation_evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "artifacts",\n "providers",\n "environments",\n "scenarios",\n "captures",\n "unknowns",\n "records"\n ],\n "type": "object"\n },\n "manifest": {\n "additionalProperties": false,\n "description": "Value for manifest.",\n "properties": {\n "bindings": {\n "description": "Value for bindings.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "fixtures": {\n "description": "Value for fixtures.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "case_kind": {\n "description": "Value for case kind.",\n "enum": [\n "positive",\n "negative",\n "malformed",\n "cancellation",\n "teardown"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "fixture_id": {\n "description": "Value for fixture id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n }\n },\n "required": [\n "fixture_id",\n "case_kind",\n "authority",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "obligation_id": {\n "description": "Value for obligation id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "original_cases": {\n "description": "Value for original cases.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "positive",\n "negative",\n "malformed",\n "cancellation",\n "teardown"\n ],\n "type": "string"\n },\n "location": {\n "description": "Value for location.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "owner": {\n "additionalProperties": false,\n "description": "Value for owner.",\n "properties": {\n "module_path": {\n "description": "Local filesystem path for module.",\n "minLength": 1,\n "type": "string"\n },\n "owner_sha256": {\n "description": "Exact SHA-256 digest of owner.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "symbol": {\n "description": "Value for symbol.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "module_path",\n "symbol",\n "owner_sha256"\n ],\n "type": "object"\n },\n "parser_type": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain_type": {\n "description": "Value for domain type.",\n "minLength": 1,\n "type": "string"\n },\n "parser": {\n "description": "Value for parser.",\n "minLength": 1,\n "type": "string"\n },\n "schema": {\n "description": "Value for schema.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "parser",\n "schema",\n "domain_type"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for parser type."\n },\n "verifier": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "claim_id": {\n "description": "Value for claim id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "command": {\n "description": "Value for command.",\n "minLength": 1,\n "type": "string"\n },\n "enumerated_obligation_ids": {\n "description": "Value for enumerated obligation ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "nondeterminism": {\n "additionalProperties": false,\n "description": "Value for nondeterminism.",\n "properties": {\n "mode": {\n "description": "Operation mode that selects the requested behavior.",\n "enum": [\n "total-order",\n "partial-order",\n "finite-traces"\n ],\n "type": "string"\n },\n "specification": {\n "description": "Value for specification.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "mode",\n "specification"\n ],\n "type": "object"\n },\n "result_evidence_id": {\n "description": "Exact Evidence identifier for the result observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "pass",\n "fail",\n "unknown",\n "unsupported",\n "skipped"\n ],\n "type": "string"\n },\n "verifier_id": {\n "description": "Value for verifier id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n }\n },\n "required": [\n "verifier_id",\n "claim_id",\n "command",\n "authority",\n "status",\n "result_evidence_id",\n "enumerated_obligation_ids",\n "nondeterminism"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for verifier."\n }\n },\n "required": [\n "obligation_id",\n "owner",\n "parser_type",\n "original_cases",\n "fixtures",\n "verifier"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "contradictions": {\n "description": "Value for contradictions.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "obligation_id": {\n "description": "Value for obligation id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n }\n },\n "required": [\n "obligation_id",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "bindings",\n "contradictions"\n ],\n "type": "object"\n },\n "reviewed_obligations": {\n "description": "Value for reviewed obligations.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "application_layer": {\n "description": "Value for application layer.",\n "enum": [\n "cli",\n "protocol",\n "electron",\n "persistence",\n "process",\n "runtime",\n "packaging",\n "native-abi",\n "application",\n "other"\n ],\n "type": "string"\n },\n "dependency_obligation_ids": {\n "description": "Value for dependency obligation ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "disposition": {\n "description": "Value for disposition.",\n "enum": [\n "active",\n "blocked",\n "out-of-scope"\n ],\n "type": "string"\n },\n "family": {\n "description": "Value for family.",\n "minLength": 1,\n "type": "string"\n },\n "obligation_id": {\n "description": "Value for obligation id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "obligation_version": {\n "description": "Value for obligation version.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "required": {\n "description": "Value for required.",\n "type": "boolean"\n },\n "required_case_kinds": {\n "description": "Value for required case kinds.",\n "items": {\n "enum": [\n "positive",\n "negative",\n "malformed",\n "cancellation",\n "teardown"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "required_fixture_authority": {\n "description": "Value for required fixture authority.",\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "required_next_evidence": {\n "description": "Value for required next evidence.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "required_original_authority": {\n "description": "Value for required original authority.",\n "enum": [\n "static",\n "runtime",\n "process",\n "external"\n ],\n "type": "string"\n },\n "required_verifier_authority": {\n "description": "Value for required verifier authority.",\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "requires_parser_type": {\n "description": "Value for requires parser type.",\n "type": "boolean"\n },\n "residual_unknown_ids": {\n "description": "Value for residual unknown ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "review_evidence_ids": {\n "description": "Ordered Evidence identifiers for the review observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "target": {\n "additionalProperties": false,\n "description": "Value for target.",\n "properties": {\n "application_node_id": {\n "anyOf": [\n {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for application node id."\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Exact SHA-256 digest of artifact."\n },\n "location": {\n "description": "Value for location.",\n "minLength": 1,\n "type": "string"\n },\n "semantic_node_id": {\n "anyOf": [\n {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for semantic node id."\n }\n },\n "required": [\n "artifact_sha256",\n "application_node_id",\n "semantic_node_id",\n "location"\n ],\n "type": "object"\n },\n "title": {\n "description": "Value for title.",\n "minLength": 1,\n "type": "string"\n },\n "unavailable_authority": {\n "description": "Value for unavailable authority.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "obligation_id",\n "obligation_version",\n "title",\n "application_layer",\n "family",\n "target",\n "required",\n "required_case_kinds",\n "required_original_authority",\n "required_fixture_authority",\n "required_verifier_authority",\n "requires_parser_type",\n "dependency_obligation_ids",\n "residual_unknown_ids",\n "unavailable_authority",\n "required_next_evidence",\n "disposition",\n "review_evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "evidence_bundle",\n "reviewed_obligations",\n "manifest"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "build_reconstruction_obligation_ledger",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "closure_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "status": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n }\n },\n "required": [\n "status"\n ],\n "type": "object"\n },\n "dependency_graph": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "depends_on_obligation_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "obligation_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n }\n },\n "required": [\n "obligation_id",\n "depends_on_obligation_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "ledger_id": {\n "pattern": "^rol_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "obligations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "application_layer": {\n "enum": [\n "cli",\n "protocol",\n "electron",\n "persistence",\n "process",\n "runtime",\n "packaging",\n "native-abi",\n "application",\n "other"\n ],\n "type": "string"\n },\n "authority_references": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "location": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "enum": [\n "candidate",\n "observed",\n "reviewed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "authority",\n "state",\n "location"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "binding": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "fixtures": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "case_kind": {\n "enum": [\n "positive",\n "negative",\n "malformed",\n "cancellation",\n "teardown"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "fixture_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n }\n },\n "required": [\n "fixture_id",\n "case_kind",\n "authority",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "obligation_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "original_cases": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "enum": [\n "positive",\n "negative",\n "malformed",\n "cancellation",\n "teardown"\n ],\n "type": "string"\n },\n "location": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "owner": {\n "additionalProperties": false,\n "properties": {\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "owner_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "symbol": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "module_path",\n "symbol",\n "owner_sha256"\n ],\n "type": "object"\n },\n "parser_type": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "domain_type": {\n "minLength": 1,\n "type": "string"\n },\n "parser": {\n "minLength": 1,\n "type": "string"\n },\n "schema": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "parser",\n "schema",\n "domain_type"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "verifier": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "claim_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "command": {\n "minLength": 1,\n "type": "string"\n },\n "enumerated_obligation_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "nondeterminism": {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "enum": [\n "total-order",\n "partial-order",\n "finite-traces"\n ],\n "type": "string"\n },\n "specification": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "mode",\n "specification"\n ],\n "type": "object"\n },\n "result_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "pass",\n "fail",\n "unknown",\n "unsupported",\n "skipped"\n ],\n "type": "string"\n },\n "verifier_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n }\n },\n "required": [\n "verifier_id",\n "claim_id",\n "command",\n "authority",\n "status",\n "result_evidence_id",\n "enumerated_obligation_ids",\n "nondeterminism"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "obligation_id",\n "owner",\n "parser_type",\n "original_cases",\n "fixtures",\n "verifier"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "dependency_obligation_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "diagnostics": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "duplicate-definition",\n "missing-owner",\n "ambiguous-owner",\n "missing-parser-type",\n "missing-original-case",\n "weak-original-case-authority",\n "missing-case",\n "weak-fixture-authority",\n "missing-verifier",\n "verifier-does-not-enumerate",\n "missing-verifier-result",\n "weak-verifier-authority",\n "verifier-failed",\n "static-only",\n "contradiction",\n "contradiction-evidence-missing",\n "residual-unknown",\n "dependency-missing",\n "dependency-open",\n "unavailable-authority",\n "blocked",\n "out-of-scope"\n ],\n "type": "string"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "code",\n "detail"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "family": {\n "minLength": 1,\n "type": "string"\n },\n "obligation_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "obligation_version": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "observed_cases": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "kind": {\n "enum": [\n "positive",\n "negative",\n "malformed",\n "cancellation",\n "teardown"\n ],\n "type": "string"\n },\n "location": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "evidence_id",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "origin": {\n "enum": [\n "generated",\n "reviewed"\n ],\n "type": "string"\n },\n "required": {\n "type": "boolean"\n },\n "required_case_kinds": {\n "items": {\n "enum": [\n "positive",\n "negative",\n "malformed",\n "cancellation",\n "teardown"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "required_fixture_authority": {\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "required_next_evidence": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "required_original_authority": {\n "enum": [\n "static",\n "runtime",\n "process",\n "external"\n ],\n "type": "string"\n },\n "required_verifier_authority": {\n "enum": [\n "unit",\n "integration",\n "protocol",\n "renderer",\n "packaged-process",\n "native-abi",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "requires_parser_type": {\n "type": "boolean"\n },\n "residual_unknown_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "source_state": {\n "enum": [\n "candidate",\n "observed",\n "reviewed",\n "unknown"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "unowned",\n "characterized",\n "implemented",\n "verified",\n "contradicted",\n "blocked",\n "out-of-scope",\n "unknown"\n ],\n "type": "string"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "application_node_id": {\n "anyOf": [\n {\n "pattern": "^jag_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "artifact_sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "location": {\n "minLength": 1,\n "type": "string"\n },\n "semantic_node_id": {\n "anyOf": [\n {\n "pattern": "^jsrg_node_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "artifact_sha256",\n "application_node_id",\n "semantic_node_id",\n "location"\n ],\n "type": "object"\n },\n "title": {\n "minLength": 1,\n "type": "string"\n },\n "unavailable_authority": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "obligation_id",\n "obligation_version",\n "title",\n "origin",\n "application_layer",\n "family",\n "target",\n "authority_references",\n "source_state",\n "observed_cases",\n "required",\n "required_case_kinds",\n "required_original_authority",\n "required_fixture_authority",\n "required_verifier_authority",\n "requires_parser_type",\n "dependency_obligation_ids",\n "residual_unknown_ids",\n "unavailable_authority",\n "required_next_evidence",\n "binding",\n "status",\n "diagnostics"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "ownership_graph": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "module_path": {\n "minLength": 1,\n "type": "string"\n },\n "obligation_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "symbol": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "obligation_id",\n "module_path",\n "symbol"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reports": {\n "additionalProperties": false,\n "properties": {\n "contradicted_obligation_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "missing_owner_obligation_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "missing_verifier_obligation_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "residual_unknown_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "missing_owner_obligation_ids",\n "missing_verifier_obligation_ids",\n "contradicted_obligation_ids",\n "residual_unknown_ids"\n ],\n "type": "object"\n },\n "schema": {\n "const": "ReconstructionObligationLedger",\n "type": "string"\n },\n "status": {\n "enum": [\n "ready",\n "open",\n "failed",\n "unknown"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "by_application_layer": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "key": {\n "enum": [\n "cli",\n "protocol",\n "electron",\n "persistence",\n "process",\n "runtime",\n "packaging",\n "native-abi",\n "application",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "key",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "by_evidence_authority": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "key": {\n "enum": [\n "candidate",\n "observed",\n "reviewed",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "key",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "by_status": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "key": {\n "enum": [\n "unowned",\n "characterized",\n "implemented",\n "verified",\n "contradicted",\n "blocked",\n "out-of-scope",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "key",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "required_open": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "verified": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "required",\n "verified",\n "required_open",\n "by_status",\n "by_application_layer",\n "by_evidence_authority"\n ],\n "type": "object"\n }\n },\n "required": [\n "schema",\n "ledger_id",\n "closure_digest",\n "status",\n "coverage",\n "summary",\n "reports",\n "ownership_graph",\n "dependency_graph",\n "obligations",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Build Reconstruction Obligation Ledger"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": true\n },\n "description": "Evaluate inline evidence-backed reconstruction coverage against one named boundary. Missing ownership or inventory is partial; stale, weak, truncated, skipped, or unresolved proof is unknown; contradictions, failed proof, missing owners, and authority routing fail closed.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": false,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "boundary_id": "replacement.cli",\n "coverage": {\n "artifacts": [],\n "boundaries": [\n {\n "allowed_dispositions": [],\n "allowed_unknown_ids": [],\n "boundary_id": "replacement.cli",\n "required_claim_ids": [\n "claim.cli.help"\n ],\n "required_package_proof_kinds": [\n "clean-install",\n "authority-independence"\n ],\n "required_surface_ids": [\n "cli.help"\n ],\n "title": "CLI replacement"\n }\n ],\n "claims": [],\n "contradictions": [],\n "evidence_bundle": {\n "artifacts": [],\n "captures": [],\n "environments": [],\n "providers": [],\n "records": [],\n "scenarios": [],\n "unknowns": []\n },\n "owners": [],\n "package_proofs": [],\n "residual_unknown_ids": [],\n "surfaces": [],\n "verifier_contracts": [],\n "verifier_results": []\n }\n }\n ],\n "properties": {\n "boundary_id": {\n "description": "Exact reconstruction boundary identifier to evaluate.",\n "minLength": 1,\n "type": "string"\n },\n "coverage": {\n "additionalProperties": false,\n "description": "Exact reconstruction-coverage commitment to verify.",\n "properties": {\n "artifacts": {\n "description": "Value for artifacts.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "description": "Value for artifact id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "artifact_sha256": {\n "description": "Exact SHA-256 digest of artifact.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "environment_sha256": {\n "description": "Exact SHA-256 digest of environment.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "artifact_id",\n "artifact_sha256",\n "version",\n "environment_sha256",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "boundaries": {\n "description": "Value for boundaries.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "allowed_dispositions": {\n "description": "Value for allowed dispositions.",\n "items": {\n "enum": [\n "external",\n "non-goal"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "allowed_unknown_ids": {\n "description": "Value for allowed unknown ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "type": "array"\n },\n "boundary_id": {\n "description": "Exact reconstruction boundary identifier to evaluate.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "required_claim_ids": {\n "description": "Value for required claim ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "required_package_proof_kinds": {\n "description": "Value for required package proof kinds.",\n "items": {\n "enum": [\n "build",\n "clean-install",\n "package-contents",\n "runtime-routing",\n "authority-independence"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "required_surface_ids": {\n "description": "Value for required surface ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "title": {\n "description": "Value for title.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "boundary_id",\n "title",\n "required_surface_ids",\n "required_claim_ids",\n "required_package_proof_kinds",\n "allowed_dispositions",\n "allowed_unknown_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "claims": {\n "description": "Value for claims.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "claim_id": {\n "description": "Value for claim id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "kind": {\n "description": "Value for kind.",\n "minLength": 1,\n "type": "string"\n },\n "required_authority": {\n "description": "Value for required authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "required_dimensions": {\n "description": "Value for required dimensions.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "surface_ids": {\n "description": "Value for surface ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "title": {\n "description": "Value for title.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "claim_id",\n "title",\n "kind",\n "surface_ids",\n "required_dimensions",\n "required_authority"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "contradictions": {\n "description": "Value for contradictions.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "claim_ids": {\n "description": "Value for claim ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "type": "array"\n },\n "contradiction_id": {\n "description": "Value for contradiction id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "active",\n "resolved"\n ],\n "type": "string"\n },\n "surface_ids": {\n "description": "Value for surface ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "contradiction_id",\n "status",\n "surface_ids",\n "claim_ids",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_bundle": {\n "description": "Value for evidence bundle.",\n "properties": {\n "artifacts": {\n "description": "Value for artifacts.",\n "items": {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n }\n },\n "required": [\n "digest",\n "format",\n "architecture"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "captures": {\n "description": "Value for captures.",\n "items": {\n "properties": {\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "predicate_type"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "environments": {\n "description": "Value for environments.",\n "items": {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "providers": {\n "description": "Value for providers.",\n "items": {\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "records": {\n "description": "Value for records.",\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links",\n "analysis_profile"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "scenarios": {\n "description": "Value for scenarios.",\n "items": {\n "properties": {\n "authority": {\n "const": "controlled-replay",\n "description": "Value for authority.",\n "type": "string"\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "authority"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "unknowns": {\n "description": "Value for unknowns.",\n "items": {\n "properties": {\n "contradicting_evidence_ids": {\n "description": "Ordered Evidence identifiers for the contradicting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "description": "Value for domain.",\n "minLength": 1,\n "type": "string"\n },\n "mutation_evidence_ids": {\n "description": "Ordered Evidence identifiers for the mutation observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "previous_revision_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for previous revision digest."\n },\n "question": {\n "description": "Concrete unresolved question to retain for later investigation.",\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "description": "Value for recommended probes.",\n "items": {\n "properties": {\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "description": "Value for relationships.",\n "items": {\n "properties": {\n "type": {\n "description": "Value for type.",\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required authority."\n },\n "required_confidence": {\n "description": "Value for required confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for id."\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for isolation."\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for platform."\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required environment."\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "properties": {\n "disposition": {\n "const": "verified",\n "description": "Value for disposition.",\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "properties": {\n "disposition": {\n "description": "Value for disposition.",\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for resolution."\n },\n "revision": {\n "description": "Value for revision.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "revision_digest": {\n "description": "Value for revision digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for scope digest."\n },\n "severity": {\n "description": "Value for severity.",\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "description": "Ordered Evidence identifiers for the supporting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "revision",\n "previous_revision_digest",\n "revision_digest",\n "scope_digest",\n "question",\n "status",\n "severity",\n "domain",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution",\n "mutation_evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "artifacts",\n "providers",\n "environments",\n "scenarios",\n "captures",\n "unknowns",\n "records"\n ],\n "type": "object"\n },\n "owners": {\n "description": "Value for owners.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "ownership": {\n "description": "Value for ownership.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "authority_route": {\n "description": "Value for authority route.",\n "enum": [\n "none",\n "detected",\n "unknown"\n ],\n "type": "string"\n },\n "disposition": {\n "const": "implemented",\n "description": "Value for disposition.",\n "type": "string"\n },\n "owner_export": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for owner export."\n },\n "owner_path": {\n "description": "Local filesystem path for owner.",\n "minLength": 1,\n "type": "string"\n },\n "owner_sha256": {\n "description": "Exact SHA-256 digest of owner.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "package_state": {\n "description": "Value for package state.",\n "enum": [\n "distributed",\n "missing",\n "unknown"\n ],\n "type": "string"\n },\n "path_state": {\n "description": "Value for path state.",\n "enum": [\n "present",\n "missing",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "owner_path",\n "owner_export",\n "owner_sha256",\n "path_state",\n "package_state",\n "authority_route"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "description": "Value for disposition.",\n "enum": [\n "external",\n "non-goal"\n ],\n "type": "string"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale"\n ],\n "type": "object"\n }\n ]\n },\n "surface_id": {\n "description": "Value for surface id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n }\n },\n "required": [\n "surface_id",\n "ownership"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "package_proofs": {\n "description": "Value for package proofs.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256s": {\n "description": "Value for artifact sha256s.",\n "items": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "kind": {\n "description": "Value for kind.",\n "enum": [\n "build",\n "clean-install",\n "package-contents",\n "runtime-routing",\n "authority-independence"\n ],\n "type": "string"\n },\n "proof_id": {\n "description": "Value for proof id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "pass",\n "fail",\n "unknown",\n "unsupported",\n "skipped"\n ],\n "type": "string"\n }\n },\n "required": [\n "proof_id",\n "kind",\n "status",\n "artifact_sha256s",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "residual_unknown_ids": {\n "description": "Value for residual unknown ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "type": "array"\n },\n "surfaces": {\n "description": "Value for surfaces.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "description": "Value for artifact id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "observed",\n "derived",\n "inferred",\n "historical",\n "external"\n ],\n "type": "string"\n },\n "dependency_surface_ids": {\n "description": "Value for dependency surface ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "type": "array"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "family": {\n "description": "Value for family.",\n "minLength": 1,\n "type": "string"\n },\n "location": {\n "description": "Value for location.",\n "minLength": 1,\n "type": "string"\n },\n "occurrence_id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for occurrence id."\n },\n "surface_id": {\n "description": "Value for surface id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n }\n },\n "required": [\n "surface_id",\n "family",\n "artifact_id",\n "occurrence_id",\n "location",\n "authority",\n "dependency_surface_ids",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "verifier_contracts": {\n "description": "Value for verifier contracts.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "live-observation",\n "external"\n ],\n "type": "string"\n },\n "claim_ids": {\n "description": "Value for claim ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "contract_sha256": {\n "description": "Exact SHA-256 digest of contract.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "dimensions": {\n "description": "Value for dimensions.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "max_age_ms": {\n "description": "Maximum permitted age ms for this operation.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "minimum_repeats": {\n "description": "Value for minimum repeats.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "normalization_removes_dimensions": {\n "const": false,\n "description": "Value for normalization removes dimensions.",\n "type": "boolean"\n },\n "normalization_sha256": {\n "description": "Exact SHA-256 digest of normalization.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "verifier_id": {\n "description": "Value for verifier id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n }\n },\n "required": [\n "verifier_id",\n "claim_ids",\n "dimensions",\n "authority",\n "max_age_ms",\n "minimum_repeats",\n "normalization_sha256",\n "normalization_removes_dimensions",\n "contract_sha256"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "verifier_results": {\n "description": "Value for verifier results.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_sha256s": {\n "description": "Value for artifact sha256s.",\n "items": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "contract_sha256": {\n "description": "Exact SHA-256 digest of contract.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "covered_claim_ids": {\n "description": "Value for covered claim ids.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "covered_dimensions": {\n "description": "Value for covered dimensions.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "normalization_sha256": {\n "description": "Exact SHA-256 digest of normalization.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "observed_at": {\n "description": "Value for observed at.",\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z|([+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)))$",\n "type": "string"\n },\n "owner_sha256s": {\n "description": "Value for owner sha256s.",\n "items": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "repeats": {\n "description": "Value for repeats.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "pass",\n "fail",\n "unknown",\n "unsupported",\n "truncated",\n "skipped"\n ],\n "type": "string"\n },\n "verifier_id": {\n "description": "Value for verifier id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n }\n },\n "required": [\n "verifier_id",\n "contract_sha256",\n "observed_at",\n "status",\n "covered_claim_ids",\n "covered_dimensions",\n "artifact_sha256s",\n "owner_sha256s",\n "normalization_sha256",\n "repeats",\n "evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "evidence_bundle",\n "artifacts",\n "surfaces",\n "owners",\n "claims",\n "verifier_contracts",\n "verifier_results",\n "residual_unknown_ids",\n "contradictions",\n "package_proofs",\n "boundaries"\n ],\n "type": "object"\n }\n },\n "required": [\n "coverage",\n "boundary_id"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "evaluate_reconstruction_coverage",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "boundary_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "reasons": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "surface-missing",\n "owner-missing",\n "owner-duplicate",\n "owner-path-missing",\n "owner-path-unknown",\n "owner-undistributed",\n "owner-distribution-unknown",\n "authority-routing-detected",\n "authority-routing-unknown",\n "disposition-not-allowed",\n "dependency-surface-missing",\n "claim-missing",\n "verifier-missing",\n "verifier-duplicate",\n "verifier-result-missing",\n "verifier-result-stale",\n "verifier-result-incompatible",\n "verifier-failed",\n "verifier-unknown",\n "active-unknown",\n "active-contradiction",\n "package-proof-missing",\n "package-proof-failed",\n "package-proof-unknown"\n ],\n "type": "string"\n },\n "detail": {\n "minLength": 1,\n "type": "string"\n },\n "subject_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n }\n },\n "required": [\n "code",\n "subject_id",\n "detail"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "recommended_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "operation": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n },\n "subject_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._:/-]{0,199}$",\n "type": "string"\n }\n },\n "required": [\n "operation",\n "subject_id",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "partial",\n "failed",\n "unknown",\n "ready"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "reasons": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "required_claims": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "required_surfaces": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "required_surfaces",\n "required_claims",\n "reasons"\n ],\n "type": "object"\n }\n },\n "required": [\n "boundary_id",\n "status",\n "summary",\n "reasons",\n "recommended_probes",\n "evidence_ids"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Evaluate Reconstruction Coverage"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Project authenticated APK inventory_artifact Evidence into an execution-free Android application inventory. Reports exact component paths and hashes, runtime-family hints, and path-based bridge hypotheses without decoding DEX, executing the APK, or claiming observed runtime calls.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "inventory_evidence": [\n {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_3cb45ae987278620b8d5a62638296cd49034f8b1dfb88d0196764a1088821e3c",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "edges": [],\n "limitations": [],\n "manifest": {\n "edge_count": 0,\n "graph_sha256": "a4cea3d85dfa8645e5eb026d0dfa82ab19b686bb15964afc5e3f874b14c76de8",\n "manifest_id": "agm_972f60433a8998c511f3b02ee759a41cd1ea2e19ba6c4133b7ac52297944dbb3",\n "node_count": 1,\n "occurrence_count": 1,\n "root_artifact_id": "art_91d55c997599a1b301a52f2ad8559a9f2bfa28927d7cb2c391f3cedf6072ccdd",\n "root_format": "apk",\n "root_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\n },\n "nodes": [\n {\n "architecture": null,\n "artifact_id": "art_91d55c997599a1b301a52f2ad8559a9f2bfa28927d7cb2c391f3cedf6072ccdd",\n "content_state": "materialized",\n "executable": false,\n "format": "apk",\n "kind": "resource",\n "limitations": [],\n "media_type": null,\n "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",\n "size": 1\n }\n ],\n "occurrences": [\n {\n "artifact_id": "art_91d55c997599a1b301a52f2ad8559a9f2bfa28927d7cb2c391f3cedf6072ccdd",\n "compressed_size": null,\n "declared_size": 1,\n "encrypted": false,\n "entry_kind": "file",\n "executable": false,\n "hash_status": "verified",\n "limitations": [],\n "logical_path": ".",\n "occurrence_id": "occ_385f341599a469f680d89371fd8d26bf235e4bd95e29f941f69830343ea72c5a",\n "parent_occurrence_id": null,\n "source_location": null\n }\n ],\n "provenance": []\n },\n "operation": "inventory_artifact",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\n },\n "format": "apk",\n "local_path": "Fixture.apk",\n "name": "Fixture.apk"\n }\n }\n ]\n }\n ],\n "properties": {\n "inventory_evidence": {\n "description": "Value for inventory evidence.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "inventory_evidence"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "project_android_application_graph",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "bridge_candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "managed-and-native-content",\n "jni-library-convention",\n "react-native-convention",\n "flutter-convention",\n "unity-convention"\n ],\n "type": "string"\n },\n "managed_path": {\n "minLength": 1,\n "type": "string"\n },\n "native_path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "managed_path",\n "native_path",\n "basis"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "components": {\n "additionalProperties": false,\n "properties": {\n "dex": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "javascript": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "jvm_classes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "manifests": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "native_libraries": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "resources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "signing": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "manifests",\n "resources",\n "dex",\n "jvm_classes",\n "native_libraries",\n "javascript",\n "signing"\n ],\n "type": "object"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "inventory_complete": {\n "type": "boolean"\n },\n "status": {\n "enum": [\n "complete-within-inventory",\n "partial"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "inventory_complete"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "projection_id": {\n "pattern": "^adp_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_format": {\n "const": "apk",\n "type": "string"\n },\n "root_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_families": {\n "items": {\n "enum": [\n "dalvik-art",\n "java-kotlin",\n "native",\n "javascript",\n "react-native",\n "flutter",\n "unity"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "source_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "projection_id",\n "root_sha256",\n "root_format",\n "source_evidence_ids",\n "components",\n "runtime_families",\n "bridge_candidates",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Project Android Application Graph"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Project authenticated IPA, macOS .app directory, ZIP, or DMG inventory Evidence into an execution-free Apple application inventory. Reports application roots, nested bundles with path-convention roles (app extensions, XPC services, login items, privileged helpers, system extensions, frameworks), launchd plists, exact component paths and hashes, runtime-family hints, and path-based bridge hypotheses without parsing plist/CMS semantics or claiming observed runtime calls.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "inventory_evidence": [\n {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_26149f40576cc43ae2d1d8bb465090d2c3b227e3d2394d08cfdf0b61beba64ad",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "edges": [],\n "limitations": [],\n "manifest": {\n "edge_count": 0,\n "graph_sha256": "5e6590160ac53eb4c5dc8a9722eec87dfc6fbee85b8b01c3391b9b5e872c862a",\n "manifest_id": "agm_72d1336aafbec396c67971ffc28c79661b2d1e2bdf8c00823db99ff007f601be",\n "node_count": 1,\n "occurrence_count": 1,\n "root_artifact_id": "art_b42a19aa08c8eda5b54ff7dedca16546a342bfe20da65c1377403feb7a74a960",\n "root_format": "ipa",\n "root_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\n },\n "nodes": [\n {\n "architecture": null,\n "artifact_id": "art_b42a19aa08c8eda5b54ff7dedca16546a342bfe20da65c1377403feb7a74a960",\n "content_state": "materialized",\n "executable": false,\n "format": "ipa",\n "kind": "resource",\n "limitations": [],\n "media_type": null,\n "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",\n "size": 1\n }\n ],\n "occurrences": [\n {\n "artifact_id": "art_b42a19aa08c8eda5b54ff7dedca16546a342bfe20da65c1377403feb7a74a960",\n "compressed_size": null,\n "declared_size": 1,\n "encrypted": false,\n "entry_kind": "file",\n "executable": false,\n "hash_status": "verified",\n "limitations": [],\n "logical_path": ".",\n "occurrence_id": "occ_fc32a68378230630d05886e51a1e327af55b3a1d89f5365e07587e7e9ed1cce6",\n "parent_occurrence_id": null,\n "source_location": null\n }\n ],\n "provenance": []\n },\n "operation": "inventory_artifact",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\n },\n "format": "ipa",\n "local_path": "Fixture.ipa",\n "name": "Fixture.ipa"\n }\n }\n ]\n },\n {\n "inventory_evidence": [\n {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_a2e645cda46d7c9b577779067e00fcd925f515a738014368b977b7be6150b5b3",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "edges": [],\n "limitations": [],\n "manifest": {\n "edge_count": 0,\n "graph_sha256": "239254d4edbe7ca89da697764e02ce8afd6fbb42e5226608fe07757f689b0bdf",\n "manifest_id": "agm_c7b02b31675948c3b62c26dd7e999a141e090e8c9c2b0631e50f3b8863d36911",\n "node_count": 1,\n "occurrence_count": 1,\n "root_artifact_id": "art_f24b49cf6eeac93cf9d7f28b66cfdc83579e311439a543b8301504a12cc20296",\n "root_format": "directory",\n "root_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"\n },\n "nodes": [\n {\n "architecture": null,\n "artifact_id": "art_f24b49cf6eeac93cf9d7f28b66cfdc83579e311439a543b8301504a12cc20296",\n "content_state": "materialized",\n "executable": false,\n "format": "directory",\n "kind": "resource",\n "limitations": [],\n "media_type": null,\n "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",\n "size": 1\n }\n ],\n "occurrences": [\n {\n "artifact_id": "art_f24b49cf6eeac93cf9d7f28b66cfdc83579e311439a543b8301504a12cc20296",\n "compressed_size": null,\n "declared_size": 1,\n "encrypted": false,\n "entry_kind": "file",\n "executable": false,\n "hash_status": "verified",\n "limitations": [],\n "logical_path": ".",\n "occurrence_id": "occ_701e5689a8053d984b5e9e047ac20498755646f679816076be952dfb73aa8b0d",\n "parent_occurrence_id": null,\n "source_location": null\n }\n ],\n "provenance": []\n },\n "operation": "inventory_artifact",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-artifact-graph",\n "name": "REA safe artifact graph provider",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"\n },\n "format": "directory",\n "local_path": "Fixture.app",\n "name": "Fixture.app"\n }\n }\n ]\n }\n ],\n "properties": {\n "inventory_evidence": {\n "description": "Value for inventory evidence.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "inventory_evidence"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "project_apple_application_graph",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "application_roots": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "bridge_candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "javascript-and-native-content",\n "react-native-convention",\n "flutter-convention",\n "unity-convention"\n ],\n "type": "string"\n },\n "native_path": {\n "minLength": 1,\n "type": "string"\n },\n "source_path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "source_path",\n "native_path",\n "basis"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "bundles": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "executable_candidates": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "info_plist_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "layout": {\n "enum": [\n "shallow",\n "macos-deep",\n "versioned-framework"\n ],\n "type": "string"\n },\n "parent_path": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "role": {\n "enum": [\n "application",\n "app-extension",\n "xpc-service",\n "framework",\n "login-item",\n "system-extension",\n "driver-extension",\n "plug-in",\n "resource-bundle",\n "bundle",\n "helper-application",\n "nested-application"\n ],\n "type": "string"\n },\n "role_basis": {\n "const": "path-convention",\n "type": "string"\n },\n "signing_paths": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "path",\n "parent_path",\n "layout",\n "role",\n "role_basis",\n "info_plist_path",\n "executable_candidates",\n "signing_paths"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "components": {\n "additionalProperties": false,\n "properties": {\n "bundle_metadata": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "executables": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "frameworks": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "helpers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "javascript": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "launchd_plists": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "domain": {\n "enum": [\n "agent",\n "daemon"\n ],\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format",\n "domain"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "native_libraries": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "privileged_helpers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "signing": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact_id": {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n "format": {\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "artifact_id",\n "sha256",\n "format"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "bundle_metadata",\n "executables",\n "frameworks",\n "native_libraries",\n "javascript",\n "signing",\n "privileged_helpers",\n "launchd_plists",\n "helpers"\n ],\n "type": "object"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "inventory_complete": {\n "type": "boolean"\n },\n "status": {\n "enum": [\n "complete-within-inventory",\n "partial"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "inventory_complete"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "platforms": {\n "items": {\n "enum": [\n "ios",\n "macos"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "projection_id": {\n "pattern": "^aap_[a-f0-9]{64}$",\n "type": "string"\n },\n "root_format": {\n "enum": [\n "ipa",\n "directory",\n "zip",\n "dmg"\n ],\n "type": "string"\n },\n "root_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "runtime_families": {\n "items": {\n "enum": [\n "native",\n "swift-objective-c",\n "javascript",\n "react-native",\n "flutter",\n "unity"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "source_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "symlinks": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "projection_id",\n "root_sha256",\n "root_format",\n "platforms",\n "source_evidence_ids",\n "application_roots",\n "bundles",\n "components",\n "symlinks",\n "runtime_families",\n "bridge_candidates",\n "coverage",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Project Apple Application Graph"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Trace one generated position in an exported website script through an explicitly paired local source map. Verifies manifest, selected script and map SHA-256/byte identities; returns every equal-position mapping, reported source/root, resolved source URL, embedded sourcesContent and original position inline. One-based lines and zero-based UTF-16 columns; greatest generated position <= requested across lines. Duplicate sources and generated-only mappings remain distinct. Caller-selected pairing does not establish deployment authenticity or execution. Regular maps and inline indexed sections only; external section URLs are unsupported. No network access or application execution; uses the pinned upstream codec in an owned Node process with private temporary files, independent 192 MiB old-generation heap and 20-second deadline. 4 MiB map, 32 MiB reply; complete evidence or actionable resource failure. Cleanup may extend the deadline.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "generated_position": {\n "column": 25,\n "line": 1\n },\n "manifest_path": "/analysis/scripts/manifest.json",\n "script_index": 0,\n "source_map": {\n "path": "/analysis/app.js.map",\n "url": "https://example.test/app.js.map"\n }\n }\n ],\n "properties": {\n "generated_position": {\n "additionalProperties": false,\n "description": "Value for generated position.",\n "properties": {\n "column": {\n "description": "Value for column.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "description": "Value for line.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "manifest_path": {\n "description": "Absolute export_web_scripts manifest path",\n "minLength": 1,\n "type": "string"\n },\n "script_index": {\n "description": "Zero-based index in manifest.scripts",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "source_map": {\n "additionalProperties": false,\n "description": "Value for source map.",\n "properties": {\n "path": {\n "description": "Absolute local source-map path; no fetch",\n "minLength": 1,\n "type": "string"\n },\n "url": {\n "description": "Explicit source-map URL context used to resolve original source names",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "url"\n ],\n "type": "object"\n }\n },\n "required": [\n "manifest_path",\n "script_index",\n "source_map",\n "generated_position"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "trace_web_source_location",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "diagnostics": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "version"\n ],\n "type": "object"\n },\n "execution": {\n "const": "unknown",\n "type": "string"\n },\n "format": {\n "enum": [\n "regular",\n "indexed"\n ],\n "type": "string"\n },\n "generated_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "lookup": {\n "additionalProperties": false,\n "properties": {\n "location_units": {\n "const": "one-based lines; zero-based UTF-16 columns",\n "type": "string"\n },\n "matched_generated_position": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "requested": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "line",\n "column"\n ],\n "type": "object"\n },\n "semantics": {\n "const": "greatest generated position <= requested, across lines; all equal-position segments",\n "type": "string"\n }\n },\n "required": [\n "requested",\n "matched_generated_position",\n "semantics",\n "location_units"\n ],\n "type": "object"\n },\n "manifest": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "capture_completeness": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n }\n ]\n },\n "capture_kind": {\n "enum": [\n "page-inspection",\n "browser-scenario"\n ],\n "type": "string"\n },\n "capture_path": {\n "minLength": 1,\n "type": "string"\n },\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "reported_output_directory": {\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_evidence_id": {\n "anyOf": [\n {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes",\n "reported_output_directory",\n "capture_path",\n "capture_sha256",\n "capture_kind",\n "capture_completeness",\n "source_evidence_id"\n ],\n "type": "object"\n },\n "matches": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "generated-only",\n "type": "string"\n }\n },\n "required": [\n "state"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "content": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "representation": {\n "const": "UTF-8 encoding of decoded sourcesContent text",\n "type": "string"\n },\n "state": {\n "const": "embedded",\n "type": "string"\n },\n "text": {\n "type": "string"\n },\n "utf8_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "utf8_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "state",\n "text",\n "utf8_sha256",\n "utf8_bytes",\n "representation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "const": "sourcesContent-not-retained",\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "flattened_source_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "ignored": {\n "type": "boolean"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "original_position": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "content_position": {\n "enum": [\n "verified-in-embedded-text",\n "outside-embedded-text",\n "content-unavailable"\n ],\n "type": "string"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "offset": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "line",\n "column",\n "offset",\n "content_position"\n ],\n "type": "object"\n },\n "reported_source": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "reported_source_root": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "resolved_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "section_path": {\n "items": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": "array"\n },\n "source_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "mapped",\n "type": "string"\n }\n },\n "required": [\n "state",\n "flattened_source_index",\n "section_path",\n "source_index",\n "reported_source",\n "reported_source_root",\n "resolved_url",\n "ignored",\n "content",\n "original_position",\n "name"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "reported_file": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "runtime": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "v8_heap_limit_bytes": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "version",\n "v8_heap_limit_bytes"\n ],\n "type": "object"\n },\n "source": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "script": {\n "additionalProperties": false,\n "properties": {\n "content": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "layout": {\n "enum": [\n "url-path",\n "isolated"\n ],\n "type": "string"\n },\n "layout_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "media_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "redacted": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "relative_path": {\n "minLength": 1,\n "type": "string"\n },\n "representation": {\n "enum": [\n "debugger-source-utf8",\n "browser-decoded-response-bytes"\n ],\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "exported",\n "type": "string"\n }\n },\n "required": [\n "state",\n "relative_path",\n "layout",\n "layout_reason",\n "sha256",\n "bytes",\n "media_type",\n "redacted",\n "representation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "message"\n ],\n "type": "object"\n }\n ]\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_module": {\n "type": "boolean"\n },\n "kind": {\n "const": "page-script",\n "type": "string"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "minLength": 1,\n "type": "string"\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "script_key",\n "frame_id",\n "is_module",\n "language",\n "source_map_url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "scenario-response",\n "type": "string"\n },\n "request_sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "response_sequence": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "anyOf": [\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "transaction_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "transaction_id",\n "request_sequence",\n "response_sequence",\n "status"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "source",\n "url",\n "content"\n ],\n "type": "object"\n },\n "script_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes",\n "script_index",\n "script"\n ],\n "type": "object"\n },\n "source_authenticity": {\n "const": "unknown",\n "type": "string"\n },\n "source_map": {\n "additionalProperties": false,\n "properties": {\n "association": {\n "const": "caller-selected",\n "type": "string"\n },\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes",\n "url",\n "association"\n ],\n "type": "object"\n },\n "source_map_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "url_context": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "source_map_sha256",\n "url_context",\n "engine",\n "runtime",\n "format",\n "reported_file",\n "lookup",\n "matches",\n "diagnostics",\n "manifest",\n "source",\n "source_map",\n "generated_offset",\n "source_authenticity",\n "execution",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Web Source Location"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Trace one exported website script\'s outgoing native ES imports/re-exports, using exact URL and optional selected import-map context. Reads a local export_web_scripts manifest and verifies the selected source\'s SHA-256/size; returns source positions, native resolved URLs or exact errors, every matching captured candidate and explicit unknown execution. Query/fragment identities are preserved. Computed imports and bundler IDs remain unknown. Select importer_url for unknown inline/document-base context. Caller-supplied Chromium executes only a trusted REA resolver stub in an owned context with page requests locally fulfilled/blocked; no captured application execution or asset refetch. Requires absolute REA_BROWSER_EXECUTABLE for literal imports. 32 MiB manifest, 16 MiB source, 4 MiB map and 20-second native deadline; cleanup may extend the deadline.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "import_map": {\n "base_url": "https://example.test/app/",\n "path": "/analysis/import-map.json"\n },\n "manifest_path": "/analysis/exported-scripts/manifest.json",\n "script_index": 0\n }\n ],\n "properties": {\n "import_map": {\n "additionalProperties": false,\n "description": "Value for import map.",\n "properties": {\n "base_url": {\n "description": "Explicit HTTP(S) base for the selected map",\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "description": "Absolute local import-map JSON path; no fetch",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "base_url"\n ],\n "type": "object"\n },\n "importer_url": {\n "description": "Explicit HTTP(S) module URL; useful when captured inline/base context is unknown",\n "minLength": 1,\n "type": "string"\n },\n "manifest_path": {\n "description": "Absolute path to an export_web_scripts manifest",\n "minLength": 1,\n "type": "string"\n },\n "script_index": {\n "description": "Zero-based index in manifest.scripts",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "manifest_path",\n "script_index"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "trace_web_module_imports",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "diagnostics": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "engine": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "version"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "import_map": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "base_url": {\n "minLength": 1,\n "type": "string"\n },\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes",\n "base_url"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "importer": {\n "additionalProperties": false,\n "properties": {\n "basis": {\n "enum": [\n "caller-selected",\n "reported-source-url"\n ],\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "url",\n "basis"\n ],\n "type": "object"\n },\n "imports": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "captured_candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "match": {\n "enum": [\n "exact-reported-url",\n "response-url-without-fragment"\n ],\n "type": "string"\n },\n "script": {\n "additionalProperties": false,\n "properties": {\n "content": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "layout": {\n "enum": [\n "url-path",\n "isolated"\n ],\n "type": "string"\n },\n "layout_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "media_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "redacted": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "relative_path": {\n "minLength": 1,\n "type": "string"\n },\n "representation": {\n "enum": [\n "debugger-source-utf8",\n "browser-decoded-response-bytes"\n ],\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "exported",\n "type": "string"\n }\n },\n "required": [\n "state",\n "relative_path",\n "layout",\n "layout_reason",\n "sha256",\n "bytes",\n "media_type",\n "redacted",\n "representation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "message"\n ],\n "type": "object"\n }\n ]\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_module": {\n "type": "boolean"\n },\n "kind": {\n "const": "page-script",\n "type": "string"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "minLength": 1,\n "type": "string"\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "script_key",\n "frame_id",\n "is_module",\n "language",\n "source_map_url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "scenario-response",\n "type": "string"\n },\n "request_sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "response_sequence": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "anyOf": [\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "transaction_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "transaction_id",\n "request_sequence",\n "response_sequence",\n "status"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "source",\n "url",\n "content"\n ],\n "type": "object"\n },\n "script_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "script_index",\n "match",\n "script"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "end": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "offset",\n "line",\n "column"\n ],\n "type": "object"\n },\n "execution": {\n "const": "unknown",\n "type": "string"\n },\n "expression": {\n "type": "string"\n },\n "kind": {\n "enum": [\n "static-import",\n "re-export",\n "dynamic-import"\n ],\n "type": "string"\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "resolved",\n "type": "string"\n },\n "url": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "state",\n "url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "type": "string"\n },\n "state": {\n "const": "rejected",\n "type": "string"\n }\n },\n "required": [\n "state",\n "message"\n ],\n "type": "object"\n }\n ]\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "const": "computed-specifier",\n "type": "string"\n },\n "state": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "specifier": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "start": {\n "additionalProperties": false,\n "properties": {\n "column": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "offset",\n "line",\n "column"\n ],\n "type": "object"\n }\n },\n "required": [\n "kind",\n "specifier",\n "expression",\n "start",\n "end",\n "resolution",\n "captured_candidates",\n "execution"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "manifest": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "capture_completeness": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n }\n ]\n },\n "capture_kind": {\n "enum": [\n "page-inspection",\n "browser-scenario"\n ],\n "type": "string"\n },\n "capture_path": {\n "minLength": 1,\n "type": "string"\n },\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "reported_output_directory": {\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "source_evidence_id": {\n "anyOf": [\n {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes",\n "reported_output_directory",\n "capture_path",\n "capture_sha256",\n "capture_kind",\n "capture_completeness",\n "source_evidence_id"\n ],\n "type": "object"\n },\n "parser": {\n "additionalProperties": false,\n "properties": {\n "diagnostics": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "location_units": {\n "const": "UTF-16 offsets and columns; one-based lines",\n "type": "string"\n },\n "state": {\n "enum": [\n "parsed",\n "unparseable"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "diagnostics",\n "location_units"\n ],\n "type": "object"\n },\n "source": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "script": {\n "additionalProperties": false,\n "properties": {\n "content": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "layout": {\n "enum": [\n "url-path",\n "isolated"\n ],\n "type": "string"\n },\n "layout_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "media_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "redacted": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "relative_path": {\n "minLength": 1,\n "type": "string"\n },\n "representation": {\n "enum": [\n "debugger-source-utf8",\n "browser-decoded-response-bytes"\n ],\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "exported",\n "type": "string"\n }\n },\n "required": [\n "state",\n "relative_path",\n "layout",\n "layout_reason",\n "sha256",\n "bytes",\n "media_type",\n "redacted",\n "representation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "message"\n ],\n "type": "object"\n }\n ]\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_module": {\n "type": "boolean"\n },\n "kind": {\n "const": "page-script",\n "type": "string"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "minLength": 1,\n "type": "string"\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "script_key",\n "frame_id",\n "is_module",\n "language",\n "source_map_url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "scenario-response",\n "type": "string"\n },\n "request_sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "response_sequence": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "anyOf": [\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "transaction_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "transaction_id",\n "request_sequence",\n "response_sequence",\n "status"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "source",\n "url",\n "content"\n ],\n "type": "object"\n },\n "script_index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes",\n "script_index",\n "script"\n ],\n "type": "object"\n }\n },\n "required": [\n "manifest",\n "source",\n "importer",\n "import_map",\n "parser",\n "engine",\n "imports",\n "diagnostics",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Trace Web Module Imports"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Export JavaScript bytes already retained in one local inspect_web_page or capture_browser_scenario JSON capture (normalized result or complete Evidence). Writes scripts and a digest-verified manifest into an absent absolute output_directory. Returns all source URLs, script or transaction/event references, unavailable states, and an analysis_input for analyze_javascript_application when any bytes were exported. Include script sources in inspect_web_page or select capture.network.response_body in scenarios. No refetch or execution. Safe unambiguous URL layouts preserve relative module paths; versions, query variants, inline scripts, and path collisions are isolated with explicit resolution limitations.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "capture_path": "/analysis/capture.json",\n "output_directory": "/analysis/exported-scripts"\n }\n ],\n "properties": {\n "capture_path": {\n "description": "Absolute filesystem path to saved inspect_web_page or capture_browser_scenario JSON, normalized result or complete Evidence",\n "minLength": 1,\n "type": "string"\n },\n "output_directory": {\n "description": "Absolute absent filesystem directory for verified scripts and manifest; parent directory must exist",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "capture_path",\n "output_directory"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "export_web_scripts",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "analysis_input": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "format": {\n "const": "directory",\n "type": "string"\n },\n "input_path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "input_path",\n "format"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "capture_completeness": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "attach_limited_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "conditions": {\n "items": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "maxItems": 3,\n "minItems": 1,\n "type": "array"\n },\n "dropped_events": {\n "additionalProperties": false,\n "properties": {\n "console_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "network_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "timeline_events": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "webmcp_tools": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_connections": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "websocket_frames": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "scripts",\n "network_requests",\n "console_events",\n "websocket_connections",\n "websocket_frames",\n "webmcp_tools",\n "timeline_events",\n "total"\n ],\n "type": "object"\n },\n "excluded": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "disallowed_origin",\n "unsupported_url",\n "unattributed_origin",\n "not_approved",\n "out_of_target_scope",\n "provider_unavailable",\n "invalid_protocol_value"\n ],\n "type": "string"\n },\n "section": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n }\n },\n "required": [\n "section",\n "reason",\n "count"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "policy_filtered_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "complete_within_window",\n "policy_filtered",\n "attach_limited",\n "truncated"\n ],\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "unavailable_sections": {\n "items": {\n "enum": [\n "frames",\n "dom",\n "accessibility",\n "scripts",\n "script_sources",\n "resources",\n "network_requests",\n "network_initiators",\n "console_events",\n "console_text",\n "websocket_connections",\n "websocket_frames",\n "websocket_shapes",\n "json_body_shapes",\n "workers",\n "storage",\n "storage_keys",\n "metadata",\n "webmcp_tools",\n "screenshot",\n "source_maps",\n "timeline"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "status",\n "conditions",\n "policy_filtered_sections",\n "attach_limited_sections",\n "truncated_sections",\n "unavailable_sections",\n "excluded",\n "dropped_events"\n ],\n "type": "object"\n },\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": true,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "status": {\n "const": "complete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "const": "incomplete",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "equality_eligible": {\n "const": false,\n "type": "boolean"\n },\n "missing_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "truncated",\n "type": "string"\n },\n "truncated_sections": {\n "items": {\n "enum": [\n "action",\n "screenshot",\n "dom",\n "accessibility",\n "url",\n "history",\n "storage",\n "events",\n "frames",\n "workers",\n "popups",\n "websockets"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "status",\n "equality_eligible",\n "missing_sections",\n "truncated_sections"\n ],\n "type": "object"\n }\n ]\n }\n ]\n },\n "capture_kind": {\n "enum": [\n "page-inspection",\n "browser-scenario"\n ],\n "type": "string"\n },\n "capture_path": {\n "minLength": 1,\n "type": "string"\n },\n "capture_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "manifest": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n "output_directory": {\n "minLength": 1,\n "type": "string"\n },\n "scripts": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "content": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "layout": {\n "enum": [\n "url-path",\n "isolated"\n ],\n "type": "string"\n },\n "layout_reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "media_type": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "redacted": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "relative_path": {\n "minLength": 1,\n "type": "string"\n },\n "representation": {\n "enum": [\n "debugger-source-utf8",\n "browser-decoded-response-bytes"\n ],\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "exported",\n "type": "string"\n }\n },\n "required": [\n "state",\n "relative_path",\n "layout",\n "layout_reason",\n "sha256",\n "bytes",\n "media_type",\n "redacted",\n "representation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "message": {\n "minLength": 1,\n "type": "string"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "message"\n ],\n "type": "object"\n }\n ]\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "is_module": {\n "type": "boolean"\n },\n "kind": {\n "const": "page-script",\n "type": "string"\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_key": {\n "minLength": 1,\n "type": "string"\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "script_key",\n "frame_id",\n "is_module",\n "language",\n "source_map_url"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "scenario-response",\n "type": "string"\n },\n "request_sequence": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "response_sequence": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "anyOf": [\n {\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "transaction_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "kind",\n "transaction_id",\n "request_sequence",\n "response_sequence",\n "status"\n ],\n "type": "object"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "source",\n "url",\n "content"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "source_evidence_id": {\n "anyOf": [\n {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "capture_path",\n "capture_sha256",\n "capture_kind",\n "source_evidence_id",\n "capture_completeness",\n "output_directory",\n "analysis_input",\n "scripts",\n "limitations",\n "manifest"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Export Web Scripts"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": true,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Instrument one selected CDP page for a finite, externally driven precise coverage window. Resets execution counters and prevents optimized execution until stopped. Returns complete nested UTF-16 ranges/counts, independently hashed script text and request initiator associations by session script ID. Does not navigate, click, evaluate selected code or close the externally owned page. Navigation ends document-scoped collection; workers and UI causality remain outside this claim. Emits an actual armed progress notification before external actions.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": true,\n "mutatesSession": true,\n "mutatesTarget": true,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222",\n "observation_ms": 10000,\n "target_id": "TARGET_ID_FROM_LIST_BROWSER_TARGETS"\n }\n ],\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "observation_ms": {\n "default": 10000,\n "description": "Value for observation ms.",\n "maximum": 2147483647,\n "minimum": 1,\n "type": "integer"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "observe_web_execution",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "coverage": {\n "additionalProperties": false,\n "properties": {\n "end_offset": {\n "const": "exclusive",\n "type": "string"\n },\n "excluded_scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "offset_units": {\n "const": "utf16-code-units",\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "scripts": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "functions": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "is_block_coverage": {\n "type": "boolean"\n },\n "name": {\n "type": "string"\n },\n "ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "count": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "end_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "source_bounds": {\n "enum": [\n "verified",\n "unknown"\n ],\n "type": "string"\n },\n "start_offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "start_offset",\n "end_offset",\n "count",\n "source_bounds"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "name",\n "is_block_coverage",\n "ranges"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "reported_url": {\n "type": "string"\n },\n "script_id": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "script_id",\n "reported_url",\n "functions"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "state": {\n "enum": [\n "captured",\n "unavailable"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "offset_units",\n "end_offset",\n "scripts",\n "excluded_scripts"\n ],\n "type": "object"\n },\n "excluded_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "instrumentation": {\n "additionalProperties": false,\n "properties": {\n "cleanup": {\n "const": "confirmed",\n "type": "string"\n },\n "disables_optimized_execution": {\n "const": true,\n "type": "boolean"\n },\n "executes_selected_code": {\n "const": false,\n "type": "boolean"\n },\n "page_ownership": {\n "const": "external",\n "type": "string"\n },\n "resets_execution_counters": {\n "const": true,\n "type": "boolean"\n },\n "takes_one_resetting_sample": {\n "type": "boolean"\n }\n },\n "required": [\n "resets_execution_counters",\n "disables_optimized_execution",\n "takes_one_resetting_sample",\n "executes_selected_code",\n "cleanup",\n "page_ownership"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "requests": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "async_parent_ids": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "debugger_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "debugger_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "callsites": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "column_number": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "function_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "line_number": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "script_id": {\n "type": "string"\n },\n "source_association": {\n "enum": [\n "script_id",\n "unknown"\n ],\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "script_id",\n "url",\n "line_number",\n "column_number",\n "function_name",\n "source_association"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "causal_attribution": {\n "const": "unknown",\n "type": "string"\n },\n "initiator_type": {\n "type": "string"\n },\n "method": {\n "type": "string"\n },\n "reported_initiator": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "request_id": {\n "minLength": 1,\n "type": "string"\n },\n "timestamp_seconds": {\n "minimum": 0,\n "type": "number"\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "request_id",\n "url",\n "method",\n "timestamp_seconds",\n "initiator_type",\n "reported_initiator",\n "callsites",\n "async_parent_ids",\n "causal_attribution"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "script_inventory": {\n "additionalProperties": false,\n "properties": {\n "coverage_absence": {\n "const": "unknown",\n "type": "string"\n },\n "main_document_scripts": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "not_reported_script_ids": {\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "main_document_scripts",\n "not_reported_script_ids",\n "coverage_absence"\n ],\n "type": "object"\n },\n "sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "execution_context_id": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "has_source_url": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "producer_hash": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "reported_execution_context": {\n "anyOf": [\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "reported_script_context_aux_data": {\n "anyOf": [\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_start": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "column_number": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line_number": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "line_number",\n "column_number"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_id": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "captured",\n "type": "string"\n },\n "text": {\n "type": "string"\n },\n "utf16_units": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "utf8_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "state",\n "text",\n "sha256",\n "utf8_bytes",\n "utf16_units"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "enum": [\n "unavailable",\n "excluded"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "script_id",\n "url",\n "execution_context_id",\n "reported_execution_context",\n "reported_script_context_aux_data",\n "frame_id",\n "producer_hash",\n "source_map_url",\n "has_source_url",\n "language",\n "resource_start",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "frame_id": {\n "minLength": 1,\n "type": "string"\n },\n "initial_url": {\n "type": "string"\n },\n "loader_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "origin": {\n "minLength": 1,\n "type": "string"\n },\n "target_id": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "origin",\n "initial_url",\n "frame_id",\n "loader_id"\n ],\n "type": "object"\n },\n "window": {\n "additionalProperties": false,\n "properties": {\n "armed_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "end_reason": {\n "enum": [\n "window_elapsed",\n "document_changed",\n "target_terminated"\n ],\n "type": "string"\n },\n "ended_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "producer_sampled_seconds": {\n "anyOf": [\n {\n "minimum": 0,\n "type": "number"\n },\n {\n "type": "null"\n }\n ]\n },\n "producer_started_seconds": {\n "minimum": 0,\n "type": "number"\n },\n "requested_ms": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "armed_at",\n "ended_at",\n "requested_ms",\n "end_reason",\n "producer_started_seconds",\n "producer_sampled_seconds"\n ],\n "type": "object"\n }\n },\n "required": [\n "browser",\n "target",\n "window",\n "coverage",\n "requests",\n "excluded_requests",\n "script_inventory",\n "sources",\n "instrumentation",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Observe Web Execution"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Inspect listeners registered directly on the first main-document CSS match in one selected CDP page. Returns event flags, native callback locations, source ownership and exact script text/digests inline. Does not dispatch events, evaluate JavaScript or invoke handlers. Delegated ancestor/framework listeners and execution history remain unknown. Releases REA\'s object group and transport, preserving the external page.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "cdp_endpoint": "http://127.0.0.1:9222",\n "selector": "#submit",\n "target_id": "TARGET_ID_FROM_LIST_BROWSER_TARGETS"\n }\n ],\n "properties": {\n "allowed_origins": {\n "description": "Value for allowed origins.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "cdp_endpoint": {\n "description": "Literal loopback Chrome DevTools Protocol endpoint.",\n "minLength": 1,\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "minLength": 1,\n "type": "string"\n },\n "target_id": {\n "description": "Exact CDP target identifier selected from the endpoint\'s target listing.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "cdp_endpoint",\n "target_id",\n "selector"\n ],\n "type": "object"\n },\n "kind": "browser-provider",\n "name": "inspect_web_event_listeners",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "browser": {\n "additionalProperties": false,\n "properties": {\n "js_version": {\n "type": "string"\n },\n "product": {\n "type": "string"\n },\n "protocol_version": {\n "type": "string"\n },\n "revision": {\n "type": "string"\n },\n "user_agent": {\n "type": "string"\n }\n },\n "required": [\n "product",\n "protocol_version",\n "revision",\n "user_agent",\n "js_version"\n ],\n "type": "object"\n },\n "cleanup": {\n "const": "confirmed",\n "type": "string"\n },\n "inspected_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "listeners": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "execution": {\n "const": "unknown",\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "column_number": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "function_name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "line_number": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "script_id": {\n "type": "string"\n },\n "source_association": {\n "enum": [\n "script_id",\n "unknown"\n ],\n "type": "string"\n },\n "url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "script_id",\n "url",\n "line_number",\n "column_number",\n "function_name",\n "source_association"\n ],\n "type": "object"\n },\n "once": {\n "type": "boolean"\n },\n "passive": {\n "type": "boolean"\n },\n "type": {\n "type": "string"\n },\n "use_capture": {\n "type": "boolean"\n }\n },\n "required": [\n "type",\n "use_capture",\n "passive",\n "once",\n "location",\n "execution"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "selected_node": {\n "additionalProperties": false,\n "properties": {\n "backend_node_id": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "match": {\n "enum": [\n "found",\n "not_found"\n ],\n "type": "string"\n },\n "selector": {\n "type": "string"\n }\n },\n "required": [\n "selector",\n "match",\n "backend_node_id"\n ],\n "type": "object"\n },\n "sources": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "execution_context_id": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "frame_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "has_source_url": {\n "anyOf": [\n {\n "type": "boolean"\n },\n {\n "type": "null"\n }\n ]\n },\n "language": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "producer_hash": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "reported_execution_context": {\n "anyOf": [\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "reported_script_context_aux_data": {\n "anyOf": [\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "resource_start": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "column_number": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "line_number": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "line_number",\n "column_number"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "script_id": {\n "minLength": 1,\n "type": "string"\n },\n "source": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "state": {\n "const": "captured",\n "type": "string"\n },\n "text": {\n "type": "string"\n },\n "utf16_units": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "utf8_bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "state",\n "text",\n "sha256",\n "utf8_bytes",\n "utf16_units"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "state": {\n "enum": [\n "unavailable",\n "excluded"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason"\n ],\n "type": "object"\n }\n ]\n },\n "source_map_url": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "url": {\n "type": "string"\n }\n },\n "required": [\n "script_id",\n "url",\n "execution_context_id",\n "reported_execution_context",\n "reported_script_context_aux_data",\n "frame_id",\n "producer_hash",\n "source_map_url",\n "has_source_url",\n "language",\n "resource_start",\n "source"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "target": {\n "additionalProperties": false,\n "properties": {\n "frame_id": {\n "minLength": 1,\n "type": "string"\n },\n "initial_url": {\n "type": "string"\n },\n "loader_id": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "origin": {\n "minLength": 1,\n "type": "string"\n },\n "target_id": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "target_id",\n "origin",\n "initial_url",\n "frame_id",\n "loader_id"\n ],\n "type": "object"\n }\n },\n "required": [\n "browser",\n "target",\n "inspected_at",\n "selected_node",\n "listeners",\n "sources",\n "cleanup",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Inspect Web Event Listeners"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Recover readable modules from one local UTF-8 JavaScript script/bundle into an absent directory, using caller-supplied Wakaru 1.13.0 on Linux x64. Returns original/derived digests, extraction byte ranges, emitted source maps, warnings and analysis_input inline. Structural splitting may fall back to one module; original names and runtime equivalence remain unknown. No application code execution. Requires util-linux prlimit; one worker, 1 GiB address-space ceiling, 120-second deadline, 64 MiB input, 128 MiB output, 10000 entries and 8 MiB per diagnostic/report stream.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "output_directory": "/tmp/recovered-app",\n "path": "/tmp/web-scripts/app.js"\n }\n ],\n "properties": {\n "extraction_mode": {\n "default": "structural",\n "description": "Structural module boundaries, heuristic fallback, or finer inspection-only regions",\n "enum": [\n "structural",\n "heuristic",\n "inspection"\n ],\n "type": "string"\n },\n "output_directory": {\n "description": "Absolute absent output directory; parent must exist",\n "minLength": 1,\n "type": "string"\n },\n "path": {\n "description": "Absolute path to a UTF-8 JavaScript file; never executed",\n "minLength": 1,\n "type": "string"\n },\n "rewrite_level": {\n "default": "standard",\n "description": "Upstream syntax recovery level; runtime equivalence remains unverified",\n "enum": [\n "minimal",\n "standard",\n "aggressive"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "output_directory"\n ],\n "type": "object"\n },\n "kind": "application",\n "name": "recover_javascript_sources",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "analysis_input": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "format": {\n "const": "directory",\n "type": "string"\n },\n "input_path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "input_path",\n "format"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "detected_formats": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "engine": {\n "additionalProperties": false,\n "properties": {\n "audited_source_revision": {\n "minLength": 1,\n "type": "string"\n },\n "executable": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n "executed_source_revision": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "version",\n "executable",\n "audited_source_revision",\n "executed_source_revision"\n ],\n "type": "object"\n },\n "failed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "manifest": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n "modules": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "artifact": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n "provenance": {\n "additionalProperties": false,\n "properties": {\n "byte_ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "context_byte_ranges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "start",\n "end"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "extraction": {\n "type": "string"\n },\n "original_path": {\n "type": "string"\n },\n "original_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "range_semantics": {\n "const": "half-open-utf8-bytes",\n "type": "string"\n },\n "reported_input": {\n "type": "string"\n }\n },\n "required": [\n "reported_input",\n "original_path",\n "original_sha256",\n "extraction",\n "byte_ranges",\n "context_byte_ranges",\n "range_semantics"\n ],\n "type": "object"\n },\n "reported_filename": {\n "minLength": 1,\n "type": "string"\n },\n "reported_status": {\n "minLength": 1,\n "type": "string"\n },\n "source_map": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "reported_filename",\n "reported_status",\n "artifact",\n "source_map",\n "provenance"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "options": {\n "additionalProperties": false,\n "properties": {\n "extraction_mode": {\n "enum": [\n "structural",\n "heuristic",\n "inspection"\n ],\n "type": "string"\n },\n "rewrite_level": {\n "enum": [\n "minimal",\n "standard",\n "aggressive"\n ],\n "type": "string"\n }\n },\n "required": [\n "extraction_mode",\n "rewrite_level"\n ],\n "type": "object"\n },\n "provenance": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n "report": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n "reported_format": {\n "type": "string"\n },\n "reported_safety": {\n "type": "string"\n },\n "reported_strategy": {\n "type": "string"\n },\n "runtime_equivalence": {\n "const": "unknown",\n "type": "string"\n },\n "source": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "published_copy": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes"\n ],\n "type": "object"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "snapshot_path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path",\n "sha256",\n "bytes",\n "snapshot_path",\n "published_copy"\n ],\n "type": "object"\n },\n "status": {\n "enum": [\n "complete",\n "partial"\n ],\n "type": "string"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "warnings": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "filename": {\n "type": "string"\n },\n "is_error": {\n "type": "boolean"\n },\n "kind": {\n "type": "string"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "filename",\n "kind",\n "is_error",\n "message"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "source",\n "engine",\n "options",\n "status",\n "detected_formats",\n "reported_safety",\n "reported_strategy",\n "reported_format",\n "total",\n "failed",\n "modules",\n "warnings",\n "report",\n "provenance",\n "manifest",\n "analysis_input",\n "runtime_equivalence",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": false,\n "title": "Recover Javascript Sources"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Open a local executable, application bundle, archive, JavaScript, source map, plist, or analysis database after validation. format=dos-com explicitly interprets 1..65280 headerless bytes as a DOS COM analysis image; omission preserves header-based detection. provider_id selects one deep provider or deterministic auto selection; the binding remains stable until close or an explicit switch, with no failure fallback. An optional analysis snapshot is imported atomically and must match the binary identity, concrete provider, and canonical analysis profile exactly.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "path": "/tmp/fixture"\n }\n ],\n "properties": {\n "format": {\n "const": "dos-com",\n "description": "Explicit headerless DOS COM interpretation; omission preserves header-based detection",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "provider_id": {\n "anyOf": [\n {\n "const": "auto",\n "type": "string"\n },\n {\n "minLength": 1,\n "pattern": "^[a-z][a-z0-9-]*$",\n "type": "string"\n }\n ],\n "description": "Exact deep-analysis provider ID, or automatic selection when omitted."\n },\n "snapshot_path": {\n "description": "Local filesystem path for snapshot.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "open_binary",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map"\n ],\n "type": "string"\n },\n "kind": {\n "enum": [\n "executable",\n "database",\n "archive",\n "artifact"\n ],\n "type": "string"\n },\n "loaderArgs": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "path",\n "format",\n "kind",\n "loaderArgs",\n "sha256",\n "architecture"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Open Binary"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": true,\n "idempotentHint": false,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Optionally write a provider-neutral analysis snapshot atomically to the caller-supplied path, then close the active target and every provider resource started for it. Existing files require explicit overwrite; a failed save leaves the session open so cached analysis is not lost.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": true,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "overwrite": false\n }\n ],\n "properties": {\n "overwrite": {\n "default": false,\n "description": "Whether an existing destination may be replaced.",\n "type": "boolean"\n },\n "snapshot_path": {\n "description": "Local filesystem path for snapshot.",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "session",\n "name": "close_binary",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "anyOf": [\n {\n "type": "null"\n },\n {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "entries": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n }\n },\n "required": [\n "path",\n "bytes",\n "entries"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Close Binary"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": true\n },\n "description": "Report the complete current target, provider, capability availability, client-feature, analysis, and server-identity status without starting analysis.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": false,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {}\n ],\n "properties": {\n "expected_catalog_digest": {\n "description": "Expected catalog digest used to reject stale or mismatched input.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "expected_package_version": {\n "description": "Expected package version used to reject stale or mismatched input.",\n "minLength": 1,\n "type": "string"\n },\n "expected_server_path": {\n "description": "Expected server path used to reject stale or mismatched input.",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "session",\n "name": "binary_session",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n },\n "__schema1": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema1"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "anyOf": [\n {\n "anyOf": [\n {\n "additionalProperties": {},\n "properties": {\n "analysis_activity": {\n "additionalProperties": false,\n "properties": {\n "providers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "active": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "caller_state": {\n "enum": [\n "waiting",\n "cancelled"\n ],\n "type": "string"\n },\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "request_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "request_id",\n "operation",\n "elapsed_ms",\n "caller_state"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "queued_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "provider",\n "active",\n "queued_requests"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "not_observed",\n "idle",\n "busy"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "providers"\n ],\n "type": "object"\n },\n "analysis_provider_binding": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "selection_source": {\n "enum": [\n "request",\n "environment",\n "auto-single-candidate"\n ],\n "type": "string"\n }\n },\n "required": [\n "provider",\n "selection_source",\n "analysis_profile"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "analysis_provider_candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "type": "null"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "available",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "not_configured",\n "executable_missing",\n "runtime_missing",\n "unsupported_host",\n "unsupported_version",\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported",\n "open_options_invalid",\n "version_unresolved"\n ],\n "type": "string"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n }\n ]\n },\n "capabilities": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "type": "null"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "type": "null"\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "anyOf": [\n {\n "enum": [\n "not_configured",\n "executable_missing",\n "runtime_missing",\n "unsupported_host",\n "unsupported_version",\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported",\n "open_options_invalid",\n "version_unresolved"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "available": {\n "const": false,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "selected": {\n "type": "boolean"\n },\n "target_support": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "type": "null"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "type": "null"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "supported",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported"\n ],\n "type": "string"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unsupported",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "provider",\n "availability",\n "target_support",\n "selected",\n "capabilities"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "analysis_run": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "process_lineage": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "status": {\n "const": "not_observed",\n "type": "string"\n }\n },\n "required": [\n "status"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "snapshots": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "observation": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "launcher_pid": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "observed_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "process_group_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "status",\n "observed_at",\n "launcher_pid",\n "process_group_id",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "descendants": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "parent_pid": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "pid": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "process_group_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "parent_pid",\n "process_group_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "launcher_parent_pid": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "launcher_pid": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "observed_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "process_group_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "const": "verified",\n "type": "string"\n }\n },\n "required": [\n "status",\n "observed_at",\n "launcher_pid",\n "process_group_id",\n "launcher_parent_pid",\n "descendants"\n ],\n "type": "object"\n }\n ]\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "observation"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "const": "snapshots",\n "type": "string"\n }\n },\n "required": [\n "status",\n "snapshots"\n ],\n "type": "object"\n }\n ]\n },\n "run_id": {\n "format": "uuid",\n "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",\n "type": "string"\n }\n },\n "required": [\n "run_id",\n "process_lineage"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "capabilities": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "type": "null"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "type": "null"\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "anyOf": [\n {\n "enum": [\n "not_configured",\n "executable_missing",\n "runtime_missing",\n "unsupported_host",\n "unsupported_version",\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported",\n "open_options_invalid",\n "version_unresolved"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "available": {\n "const": false,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "client_features": {\n "additionalProperties": false,\n "properties": {\n "elicitation_form": {\n "type": "boolean"\n },\n "elicitation_url": {\n "type": "boolean"\n },\n "roots": {\n "type": "boolean"\n },\n "sampling": {\n "type": "boolean"\n }\n },\n "required": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "object"\n },\n "open": {\n "const": false,\n "type": "boolean"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "providers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "server_identity": {\n "additionalProperties": false,\n "properties": {\n "alignment": {\n "additionalProperties": false,\n "properties": {\n "reasons": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "state": {\n "enum": [\n "aligned",\n "mcp_server_restart_required",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "reasons",\n "remediation"\n ],\n "type": "object"\n },\n "catalog": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "client": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "version": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "version"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "negotiated_protocol_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "package": {\n "additionalProperties": false,\n "properties": {\n "build_commit": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "root_path": {\n "type": "string"\n },\n "version": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "root_path",\n "build_commit"\n ],\n "type": "object"\n },\n "protocol_features": {\n "additionalProperties": false,\n "properties": {\n "cancellation": {\n "type": "boolean"\n },\n "elicitation": {\n "type": "boolean"\n },\n "progress": {\n "type": "boolean"\n }\n },\n "required": [\n "progress",\n "cancellation",\n "elicitation"\n ],\n "type": "object"\n },\n "sdk": {\n "additionalProperties": false,\n "properties": {\n "client_test": {\n "type": "string"\n },\n "core": {\n "type": "string"\n },\n "server": {\n "type": "string"\n }\n },\n "required": [\n "server",\n "client_test",\n "core"\n ],\n "type": "object"\n },\n "server": {\n "additionalProperties": false,\n "properties": {\n "command_path": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "started_at": {\n "type": "string"\n },\n "version": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "started_at",\n "command_path"\n ],\n "type": "object"\n },\n "skill": {\n "additionalProperties": false,\n "properties": {\n "expected_version": {\n "type": "string"\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "expected_version"\n ],\n "type": "object"\n }\n },\n "required": [\n "package",\n "server",\n "sdk",\n "negotiated_protocol_version",\n "client",\n "skill",\n "catalog",\n "protocol_features",\n "alignment"\n ],\n "type": "object"\n },\n "tool_availability": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "annotations": {\n "additionalProperties": false,\n "properties": {\n "destructive": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "open_world": {\n "type": "boolean"\n },\n "read_only": {\n "type": "boolean"\n }\n },\n "required": [\n "read_only",\n "destructive",\n "idempotent",\n "open_world"\n ],\n "type": "object"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "client_requirements": {\n "additionalProperties": false,\n "properties": {\n "missing_optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "missing_required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "required",\n "optional",\n "missing_required",\n "missing_optional"\n ],\n "type": "object"\n },\n "default_mode_available": {\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "accessesNetwork": {\n "type": "boolean"\n },\n "changesUiState": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "launchesProcess": {\n "type": "boolean"\n },\n "mayDiscardData": {\n "type": "boolean"\n },\n "mutatesSession": {\n "type": "boolean"\n },\n "mutatesTarget": {\n "type": "boolean"\n },\n "writesFilesystem": {\n "type": "boolean"\n }\n },\n "required": [\n "mutatesTarget",\n "mutatesSession",\n "writesFilesystem",\n "launchesProcess",\n "accessesNetwork",\n "changesUiState",\n "mayDiscardData",\n "idempotent"\n ],\n "type": "object"\n },\n "modes": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "type": "null"\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "reason": {\n "const": "available",\n "type": "string"\n },\n "remediation": {\n "type": "null"\n },\n "surface": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "surface",\n "client_requirements",\n "effects",\n "annotations",\n "available",\n "reason",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "annotations": {\n "additionalProperties": false,\n "properties": {\n "destructive": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "open_world": {\n "type": "boolean"\n },\n "read_only": {\n "type": "boolean"\n }\n },\n "required": [\n "read_only",\n "destructive",\n "idempotent",\n "open_world"\n ],\n "type": "object"\n },\n "available": {\n "const": false,\n "type": "boolean"\n },\n "client_requirements": {\n "additionalProperties": false,\n "properties": {\n "missing_optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "missing_required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "required",\n "optional",\n "missing_required",\n "missing_optional"\n ],\n "type": "object"\n },\n "default_mode_available": {\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "accessesNetwork": {\n "type": "boolean"\n },\n "changesUiState": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "launchesProcess": {\n "type": "boolean"\n },\n "mayDiscardData": {\n "type": "boolean"\n },\n "mutatesSession": {\n "type": "boolean"\n },\n "mutatesTarget": {\n "type": "boolean"\n },\n "writesFilesystem": {\n "type": "boolean"\n }\n },\n "required": [\n "mutatesTarget",\n "mutatesSession",\n "writesFilesystem",\n "launchesProcess",\n "accessesNetwork",\n "changesUiState",\n "mayDiscardData",\n "idempotent"\n ],\n "type": "object"\n },\n "modes": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "type": "null"\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "reason": {\n "enum": [\n "client_capability_missing",\n "target_required",\n "provider_missing",\n "provider_unavailable",\n "target_unsupported",\n "unsupported_host"\n ],\n "type": "string"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "surface": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "surface",\n "client_requirements",\n "effects",\n "annotations",\n "available",\n "reason",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "provider",\n "providers",\n "capabilities",\n "analysis_run",\n "analysis_activity",\n "analysis_provider_binding",\n "analysis_provider_candidates",\n "tool_availability",\n "client_features",\n "server_identity",\n "open"\n ],\n "type": "object"\n },\n {\n "additionalProperties": {},\n "properties": {\n "analysis_activity": {\n "additionalProperties": false,\n "properties": {\n "providers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "active": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "caller_state": {\n "enum": [\n "waiting",\n "cancelled"\n ],\n "type": "string"\n },\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "request_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "request_id",\n "operation",\n "elapsed_ms",\n "caller_state"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "queued_requests": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "provider",\n "active",\n "queued_requests"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "not_observed",\n "idle",\n "busy"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "providers"\n ],\n "type": "object"\n },\n "analysis_provider_binding": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "selection_source": {\n "enum": [\n "request",\n "environment",\n "auto-single-candidate"\n ],\n "type": "string"\n }\n },\n "required": [\n "provider",\n "selection_source",\n "analysis_profile"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "analysis_provider_candidates": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "availability": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "type": "null"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "available",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "not_configured",\n "executable_missing",\n "runtime_missing",\n "unsupported_host",\n "unsupported_version",\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported",\n "open_options_invalid",\n "version_unresolved"\n ],\n "type": "string"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n }\n ]\n },\n "capabilities": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "type": "null"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "type": "null"\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "anyOf": [\n {\n "enum": [\n "not_configured",\n "executable_missing",\n "runtime_missing",\n "unsupported_host",\n "unsupported_version",\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported",\n "open_options_invalid",\n "version_unresolved"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "available": {\n "const": false,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "selected": {\n "type": "boolean"\n },\n "target_support": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "type": "null"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "type": "null"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "type": "null"\n },\n "status": {\n "const": "supported",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported"\n ],\n "type": "string"\n },\n "diagnostics": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unsupported",\n "type": "string"\n }\n },\n "required": [\n "status",\n "code",\n "reason",\n "diagnostics"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "provider",\n "availability",\n "target_support",\n "selected",\n "capabilities"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "analysis_run": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "process_lineage": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "status": {\n "const": "not_observed",\n "type": "string"\n }\n },\n "required": [\n "status"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "snapshots": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "observation": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "launcher_pid": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "observed_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "process_group_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "reason": {\n "minLength": 1,\n "type": "string"\n },\n "status": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "status",\n "observed_at",\n "launcher_pid",\n "process_group_id",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "descendants": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "parent_pid": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "pid": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "process_group_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n }\n },\n "required": [\n "pid",\n "parent_pid",\n "process_group_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "launcher_parent_pid": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "launcher_pid": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "observed_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "process_group_id": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "status": {\n "const": "verified",\n "type": "string"\n }\n },\n "required": [\n "status",\n "observed_at",\n "launcher_pid",\n "process_group_id",\n "launcher_parent_pid",\n "descendants"\n ],\n "type": "object"\n }\n ]\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "observation"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "status": {\n "const": "snapshots",\n "type": "string"\n }\n },\n "required": [\n "status",\n "snapshots"\n ],\n "type": "object"\n }\n ]\n },\n "run_id": {\n "format": "uuid",\n "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",\n "type": "string"\n }\n },\n "required": [\n "run_id",\n "process_lineage"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "capabilities": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "type": "null"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "type": "null"\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "availability_code": {\n "anyOf": [\n {\n "enum": [\n "not_configured",\n "executable_missing",\n "runtime_missing",\n "unsupported_host",\n "unsupported_version",\n "target_kind_unsupported",\n "target_format_unsupported",\n "architecture_unsupported",\n "target_role_unsupported",\n "managed_target_unsupported",\n "open_options_invalid",\n "version_unresolved"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "available": {\n "const": false,\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "changes_permissions": {\n "type": "boolean"\n },\n "launches_process": {\n "type": "boolean"\n },\n "may_access_network": {\n "type": "boolean"\n },\n "may_show_ui": {\n "type": "boolean"\n },\n "may_write_filesystem": {\n "type": "boolean"\n },\n "mutates_artifact": {\n "type": "boolean"\n },\n "requires_root": {\n "type": "boolean"\n }\n },\n "required": [\n "mutates_artifact",\n "launches_process",\n "may_show_ui",\n "may_access_network",\n "may_write_filesystem",\n "changes_permissions",\n "requires_root"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "operation": {\n "type": "string"\n },\n "reason": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "operation",\n "effects",\n "limitations",\n "available",\n "reason",\n "availability_code"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "client_features": {\n "additionalProperties": false,\n "properties": {\n "elicitation_form": {\n "type": "boolean"\n },\n "elicitation_url": {\n "type": "boolean"\n },\n "roots": {\n "type": "boolean"\n },\n "sampling": {\n "type": "boolean"\n }\n },\n "required": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map"\n ],\n "type": "string"\n },\n "kind": {\n "enum": [\n "executable",\n "database",\n "archive",\n "artifact"\n ],\n "type": "string"\n },\n "open": {\n "const": true,\n "type": "boolean"\n },\n "path": {\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "providers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "server_identity": {\n "additionalProperties": false,\n "properties": {\n "alignment": {\n "additionalProperties": false,\n "properties": {\n "reasons": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "state": {\n "enum": [\n "aligned",\n "mcp_server_restart_required",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "reasons",\n "remediation"\n ],\n "type": "object"\n },\n "catalog": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema1"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "client": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string"\n },\n "version": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "version"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "negotiated_protocol_version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "package": {\n "additionalProperties": false,\n "properties": {\n "build_commit": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "name": {\n "type": "string"\n },\n "root_path": {\n "type": "string"\n },\n "version": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "root_path",\n "build_commit"\n ],\n "type": "object"\n },\n "protocol_features": {\n "additionalProperties": false,\n "properties": {\n "cancellation": {\n "type": "boolean"\n },\n "elicitation": {\n "type": "boolean"\n },\n "progress": {\n "type": "boolean"\n }\n },\n "required": [\n "progress",\n "cancellation",\n "elicitation"\n ],\n "type": "object"\n },\n "sdk": {\n "additionalProperties": false,\n "properties": {\n "client_test": {\n "type": "string"\n },\n "core": {\n "type": "string"\n },\n "server": {\n "type": "string"\n }\n },\n "required": [\n "server",\n "client_test",\n "core"\n ],\n "type": "object"\n },\n "server": {\n "additionalProperties": false,\n "properties": {\n "command_path": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "started_at": {\n "type": "string"\n },\n "version": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "version",\n "started_at",\n "command_path"\n ],\n "type": "object"\n },\n "skill": {\n "additionalProperties": false,\n "properties": {\n "expected_version": {\n "type": "string"\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "expected_version"\n ],\n "type": "object"\n }\n },\n "required": [\n "package",\n "server",\n "sdk",\n "negotiated_protocol_version",\n "client",\n "skill",\n "catalog",\n "protocol_features",\n "alignment"\n ],\n "type": "object"\n },\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "tool_availability": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "annotations": {\n "additionalProperties": false,\n "properties": {\n "destructive": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "open_world": {\n "type": "boolean"\n },\n "read_only": {\n "type": "boolean"\n }\n },\n "required": [\n "read_only",\n "destructive",\n "idempotent",\n "open_world"\n ],\n "type": "object"\n },\n "available": {\n "const": true,\n "type": "boolean"\n },\n "client_requirements": {\n "additionalProperties": false,\n "properties": {\n "missing_optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "missing_required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "required",\n "optional",\n "missing_required",\n "missing_optional"\n ],\n "type": "object"\n },\n "default_mode_available": {\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "accessesNetwork": {\n "type": "boolean"\n },\n "changesUiState": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "launchesProcess": {\n "type": "boolean"\n },\n "mayDiscardData": {\n "type": "boolean"\n },\n "mutatesSession": {\n "type": "boolean"\n },\n "mutatesTarget": {\n "type": "boolean"\n },\n "writesFilesystem": {\n "type": "boolean"\n }\n },\n "required": [\n "mutatesTarget",\n "mutatesSession",\n "writesFilesystem",\n "launchesProcess",\n "accessesNetwork",\n "changesUiState",\n "mayDiscardData",\n "idempotent"\n ],\n "type": "object"\n },\n "modes": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "type": "null"\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "reason": {\n "const": "available",\n "type": "string"\n },\n "remediation": {\n "type": "null"\n },\n "surface": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "surface",\n "client_requirements",\n "effects",\n "annotations",\n "available",\n "reason",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "annotations": {\n "additionalProperties": false,\n "properties": {\n "destructive": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "open_world": {\n "type": "boolean"\n },\n "read_only": {\n "type": "boolean"\n }\n },\n "required": [\n "read_only",\n "destructive",\n "idempotent",\n "open_world"\n ],\n "type": "object"\n },\n "available": {\n "const": false,\n "type": "boolean"\n },\n "client_requirements": {\n "additionalProperties": false,\n "properties": {\n "missing_optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "missing_required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "optional": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "required": {\n "items": {\n "enum": [\n "elicitation_form",\n "elicitation_url",\n "roots",\n "sampling"\n ],\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "required",\n "optional",\n "missing_required",\n "missing_optional"\n ],\n "type": "object"\n },\n "default_mode_available": {\n "type": "boolean"\n },\n "effects": {\n "additionalProperties": false,\n "properties": {\n "accessesNetwork": {\n "type": "boolean"\n },\n "changesUiState": {\n "type": "boolean"\n },\n "idempotent": {\n "type": "boolean"\n },\n "launchesProcess": {\n "type": "boolean"\n },\n "mayDiscardData": {\n "type": "boolean"\n },\n "mutatesSession": {\n "type": "boolean"\n },\n "mutatesTarget": {\n "type": "boolean"\n },\n "writesFilesystem": {\n "type": "boolean"\n }\n },\n "required": [\n "mutatesTarget",\n "mutatesSession",\n "writesFilesystem",\n "launchesProcess",\n "accessesNetwork",\n "changesUiState",\n "mayDiscardData",\n "idempotent"\n ],\n "type": "object"\n },\n "modes": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": true,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "type": "null"\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "available": {\n "const": false,\n "type": "boolean"\n },\n "missing_operations": {\n "items": {\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "name",\n "available",\n "missing_operations",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "name": {\n "type": "string"\n },\n "reason": {\n "enum": [\n "client_capability_missing",\n "target_required",\n "provider_missing",\n "provider_unavailable",\n "target_unsupported",\n "unsupported_host"\n ],\n "type": "string"\n },\n "remediation": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "surface": {\n "type": "string"\n }\n },\n "required": [\n "name",\n "surface",\n "client_requirements",\n "effects",\n "annotations",\n "available",\n "reason",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "provider",\n "providers",\n "capabilities",\n "analysis_run",\n "analysis_activity",\n "analysis_provider_binding",\n "analysis_provider_candidates",\n "tool_availability",\n "client_features",\n "server_identity",\n "open",\n "path",\n "format",\n "kind",\n "sha256",\n "architecture"\n ],\n "type": "object"\n }\n ]\n }\n ]\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Binary Session"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": true,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Atomically write the session\'s deterministic Evidence bundle to the requested local path. Existing files require overwrite: true; records and manifests use canonical byte-stable ordering.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": true,\n "mutatesSession": false,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "overwrite": false,\n "path": "/tmp/evidence.json"\n }\n ],\n "properties": {\n "overwrite": {\n "default": false,\n "description": "Whether an existing destination may be replaced.",\n "type": "boolean"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "export_evidence_bundle",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "bytes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "records": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknowns": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "path",\n "bytes",\n "records",\n "unknowns"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Export Evidence Bundle"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Read the JSON bundle at the supplied local path, validate every Evidence ID and canonical manifest, then atomically merge it. Imported content is data only and is never executed.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "path": "evidence.json"\n }\n ],\n "properties": {\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "path"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "import_evidence_bundle",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "imported": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknowns_added": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "imported",\n "unknowns_added",\n "total"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Import Evidence Bundle"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": true,\n "readOnlyHint": false\n },\n "description": "Run one caller-selected command under a PTY and return process capture Evidence with residual unknowns. A command name resolves through the inherited PATH; the working directory defaults to the caller\'s current directory; host environment variables are inherited with scenario overrides. Environment keys cannot contain \'=\' or NUL, and REA_PROCESS_RUN_ID is reserved for process ownership. Filesystem snapshots are opt-in through filesystem_observation_paths. The target runs with the current user\'s permissions; this is not a security sandbox.",\n "effects": {\n "accessesNetwork": true,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": true,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": true\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "arguments": [],\n "environment": {},\n "events": [],\n "executable": "/usr/bin/true",\n "filesystem_observation_paths": [],\n "idle_timeout_ms": 30000,\n "limits": {\n "file_bytes": 10000000,\n "files": 10000,\n "filesystem_depth": 16,\n "output_bytes": 1000000,\n "processes": 1000\n },\n "normalization": {\n "paths": false,\n "patterns": [],\n "pids": true,\n "ports": true,\n "time_bucket_ms": 10\n },\n "settle_ms": 100,\n "terminal": {\n "columns": 80,\n "rows": 24,\n "scrollback": 1000\n },\n "timeout_ms": 30000,\n "working_directory": "/tmp"\n }\n ],\n "properties": {\n "arguments": {\n "default": [],\n "description": "Value for arguments.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "environment": {\n "additionalProperties": {\n "type": "string"\n },\n "default": {},\n "description": "Value for environment.",\n "propertyNames": {\n "minLength": 1,\n "pattern": "^(?!REA_PROCESS_RUN_ID(?![\\\\s\\\\S]))[^=\\\\0]+$",\n "type": "string"\n },\n "type": "object"\n },\n "events": {\n "default": [],\n "description": "Value for events.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "at_ms": {\n "description": "Value for at ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "data": {\n "description": "Value for data.",\n "type": "string"\n },\n "sensitive": {\n "default": false,\n "description": "Value for sensitive.",\n "type": "boolean"\n },\n "type": {\n "const": "input",\n "description": "Value for type.",\n "type": "string"\n }\n },\n "required": [\n "at_ms",\n "type",\n "data"\n ],\n "type": "object"\n },\n {\n "properties": {\n "at_ms": {\n "description": "Value for at ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "columns": {\n "description": "Value for columns.",\n "maximum": 65535,\n "minimum": 1,\n "type": "integer"\n },\n "rows": {\n "description": "Value for rows.",\n "maximum": 65535,\n "minimum": 1,\n "type": "integer"\n },\n "type": {\n "const": "resize",\n "description": "Value for type.",\n "type": "string"\n }\n },\n "required": [\n "at_ms",\n "type",\n "columns",\n "rows"\n ],\n "type": "object"\n },\n {\n "properties": {\n "at_ms": {\n "description": "Value for at ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "signal": {\n "description": "Value for signal.",\n "enum": [\n "SIGINT",\n "SIGTERM",\n "SIGKILL"\n ],\n "type": "string"\n },\n "type": {\n "const": "signal",\n "description": "Value for type.",\n "type": "string"\n }\n },\n "required": [\n "at_ms",\n "type",\n "signal"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "executable": {\n "description": "Command name or executable path to run.",\n "minLength": 1,\n "type": "string"\n },\n "filesystem_observation_paths": {\n "default": [],\n "description": "Value for filesystem observation paths.",\n "items": {\n "minLength": 1,\n "type": "string"\n },\n "type": "array"\n },\n "idle_timeout_ms": {\n "default": 30000,\n "description": "Value for idle timeout ms.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "limits": {\n "additionalProperties": false,\n "default": {\n "file_bytes": 10000000,\n "files": 10000,\n "filesystem_depth": 16,\n "output_bytes": 1000000,\n "processes": 1000\n },\n "description": "Resource budgets that bound execution and retained output for this operation.",\n "properties": {\n "file_bytes": {\n "default": 10000000,\n "description": "Byte count for file.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "files": {\n "default": 10000,\n "description": "Value for files.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "filesystem_depth": {\n "default": 16,\n "description": "Value for filesystem depth.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "output_bytes": {\n "default": 1000000,\n "description": "Byte count for output.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "processes": {\n "default": 1000,\n "description": "Value for processes.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "type": "object"\n },\n "normalization": {\n "default": {\n "paths": false,\n "patterns": [],\n "pids": true,\n "ports": true,\n "time_bucket_ms": 10\n },\n "description": "Value for normalization.",\n "properties": {\n "paths": {\n "default": false,\n "description": "Value for paths.",\n "type": "boolean"\n },\n "patterns": {\n "default": [],\n "description": "Value for patterns.",\n "items": {\n "properties": {\n "pattern": {\n "description": "Literal text or regular expression used to filter matching results.",\n "type": "string"\n },\n "replacement": {\n "description": "Value for replacement.",\n "type": "string"\n }\n },\n "required": [\n "pattern",\n "replacement"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "pids": {\n "default": true,\n "description": "Value for pids.",\n "type": "boolean"\n },\n "ports": {\n "default": true,\n "description": "Value for ports.",\n "type": "boolean"\n },\n "time_bucket_ms": {\n "default": 10,\n "description": "Value for time bucket ms.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "type": "object"\n },\n "settle_ms": {\n "default": 100,\n "description": "Value for settle ms.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "terminal": {\n "default": {\n "columns": 80,\n "rows": 24,\n "scrollback": 1000\n },\n "description": "Value for terminal.",\n "properties": {\n "columns": {\n "default": 80,\n "description": "Value for columns.",\n "maximum": 65535,\n "minimum": 1,\n "type": "integer"\n },\n "rows": {\n "default": 24,\n "description": "Value for rows.",\n "maximum": 65535,\n "minimum": 1,\n "type": "integer"\n },\n "scrollback": {\n "default": 1000,\n "description": "Value for scrollback.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "type": "object"\n },\n "timeout_ms": {\n "default": 30000,\n "description": "Value for timeout ms.",\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "working_directory": {\n "default": ".",\n "description": "Value for working directory.",\n "type": "string"\n }\n },\n "required": [\n "executable"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "capture_process_scenario",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "cleanup": {\n "additionalProperties": false,\n "properties": {\n "owned_process_group": {\n "const": "verified",\n "type": "string"\n },\n "temporary_root": {\n "const": "removed",\n "type": "string"\n }\n },\n "required": [\n "owned_process_group",\n "temporary_root"\n ],\n "type": "object"\n },\n "event_journal": {\n "default": [],\n "items": {\n "additionalProperties": false,\n "properties": {\n "capture_order": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "collection": {\n "enum": [\n "frames",\n "rendered_frames",\n "interaction_events",\n "lifecycle",\n "process_samples",\n "filesystem_checkpoints"\n ],\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "capture_order",\n "collection",\n "index"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "exit": {\n "additionalProperties": false,\n "properties": {\n "code": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "reason": {\n "enum": [\n "exited",\n "timeout",\n "idle_timeout"\n ],\n "type": "string"\n },\n "signal": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": -9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "code",\n "signal",\n "reason"\n ],\n "type": "object"\n },\n "files_after": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "files_before": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "filesystem_checkpoints": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "at_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "effects": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "before": {\n "type": "null"\n },\n "path": {\n "type": "string"\n },\n "status": {\n "const": "created",\n "type": "string"\n }\n },\n "required": [\n "path",\n "status",\n "before",\n "after"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "type": "null"\n },\n "before": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "path": {\n "type": "string"\n },\n "status": {\n "const": "deleted",\n "type": "string"\n }\n },\n "required": [\n "path",\n "status",\n "before",\n "after"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "before": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "path": {\n "type": "string"\n },\n "status": {\n "enum": [\n "modified",\n "unchanged"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "status",\n "before",\n "after"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "files": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "name": {\n "enum": [\n "before",\n "after_settlement"\n ],\n "type": "string"\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "name",\n "at_ms",\n "files",\n "effects",\n "truncated"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "filesystem_effects": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "before": {\n "type": "null"\n },\n "path": {\n "type": "string"\n },\n "status": {\n "const": "created",\n "type": "string"\n }\n },\n "required": [\n "path",\n "status",\n "before",\n "after"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "type": "null"\n },\n "before": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "path": {\n "type": "string"\n },\n "status": {\n "const": "deleted",\n "type": "string"\n }\n },\n "required": [\n "path",\n "status",\n "before",\n "after"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "after": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "before": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "const": "file",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "string"\n },\n "type": {\n "const": "symlink",\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "mode": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "path": {\n "type": "string"\n },\n "sha256": {\n "type": "null"\n },\n "size": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "symlink_target": {\n "type": "null"\n },\n "type": {\n "enum": [\n "directory",\n "other"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "mode",\n "size",\n "type",\n "sha256",\n "symlink_target"\n ],\n "type": "object"\n }\n ]\n },\n "path": {\n "type": "string"\n },\n "status": {\n "enum": [\n "modified",\n "unchanged"\n ],\n "type": "string"\n }\n },\n "required": [\n "path",\n "status",\n "before",\n "after"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "frames": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "at_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "data": {\n "type": "string"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "sequence",\n "at_ms",\n "data"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "interaction_events": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "data": {\n "type": "string"\n },\n "dispatched_at_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "outcome": {\n "enum": [\n "dispatched",\n "target_exited",\n "failed"\n ],\n "type": "string"\n },\n "scheduled_at_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "type": {\n "enum": [\n "input",\n "resize",\n "signal"\n ],\n "type": "string"\n }\n },\n "required": [\n "sequence",\n "scheduled_at_ms",\n "dispatched_at_ms",\n "type",\n "data",\n "outcome"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "manifest": {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "comparison_contract": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "comparison_contract_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "completed_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n },\n "executable_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "full_scenario_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "normalization_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n },\n "provider_version": {\n "minLength": 1,\n "type": "string"\n },\n "pty_backend": {\n "const": "node-pty",\n "type": "string"\n },\n "rea_version": {\n "minLength": 1,\n "type": "string"\n },\n "scenario": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "started_at": {\n "format": "date-time",\n "pattern": "^(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))T(?:(?:[01]\\\\d|2[0-3]):[0-5]\\\\d(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?(?:Z))$",\n "type": "string"\n }\n },\n "required": [\n "rea_version",\n "provider_version",\n "platform",\n "architecture",\n "pty_backend",\n "started_at",\n "completed_at",\n "scenario",\n "comparison_contract",\n "full_scenario_sha256",\n "comparison_contract_sha256",\n "executable_sha256",\n "normalization_sha256"\n ],\n "type": "object"\n },\n "normalization": {\n "additionalProperties": false,\n "properties": {\n "paths": {\n "type": "boolean"\n },\n "patterns": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "pattern": {\n "type": "string"\n },\n "replacement": {\n "type": "string"\n }\n },\n "required": [\n "pattern",\n "replacement"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "pids": {\n "type": "boolean"\n },\n "ports": {\n "type": "boolean"\n },\n "time_bucket_ms": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n }\n },\n "required": [\n "paths",\n "pids",\n "ports",\n "time_bucket_ms",\n "patterns"\n ],\n "type": "object"\n },\n "process_samples": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "at_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "command": {\n "type": "string"\n },\n "parent_pid": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "pid": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "process_group_id": {\n "anyOf": [\n {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "session_id": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "at_ms",\n "pid",\n "parent_pid",\n "command",\n "process_group_id",\n "session_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "rendered_frames": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "active_buffer": {\n "enum": [\n "normal",\n "alternate"\n ],\n "type": "string"\n },\n "at_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "columns": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "cursor_x": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "cursor_y": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "lines": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "rows": {\n "exclusiveMinimum": 0,\n "maximum": 9007199254740991,\n "type": "integer"\n },\n "sequence": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "serialized_state": {\n "type": "string"\n }\n },\n "required": [\n "sequence",\n "at_ms",\n "columns",\n "rows",\n "cursor_x",\n "cursor_y",\n "active_buffer",\n "lines",\n "serialized_state"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "residual_unknowns": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "type": "string"\n },\n "scope": {\n "enum": [\n "terminal",\n "interaction",\n "exit",\n "process",\n "filesystem",\n "cleanup",\n "network",\n "environment"\n ],\n "type": "string"\n }\n },\n "required": [\n "scope",\n "reason"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "settlement": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "cleanup_outcome": {\n "const": "not_required",\n "type": "string"\n },\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "const": "quiesced",\n "type": "string"\n }\n },\n "required": [\n "state",\n "elapsed_ms",\n "cleanup_outcome"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "cleanup_outcome": {\n "enum": [\n "cleaned",\n "failed"\n ],\n "type": "string"\n },\n "elapsed_ms": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "state": {\n "enum": [\n "alive_at_deadline",\n "unverifiable"\n ],\n "type": "string"\n }\n },\n "required": [\n "state",\n "elapsed_ms",\n "cleanup_outcome"\n ],\n "type": "object"\n }\n ]\n },\n "truncated": {\n "type": "boolean"\n }\n },\n "required": [\n "manifest",\n "normalization",\n "frames",\n "rendered_frames",\n "interaction_events",\n "exit",\n "settlement",\n "process_samples",\n "filesystem_checkpoints",\n "event_journal",\n "files_before",\n "files_after",\n "filesystem_effects",\n "truncated",\n "limitations",\n "residual_unknowns",\n "cleanup"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Capture Process Scenario"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare two compatible process capture observations across terminal, interaction, lifecycle, process, filesystem, command-shim, HTTP, and WebSocket evidence. Optional trace_spec validates exact events against an explicit partial order or finite trace language; concurrency is never inferred from timestamps or broad sorting. Missing, journal-free, or truncated observations are never treated as equivalent.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "left": {\n "authority": "controlled-replay",\n "confidence": "observed",\n "environment": {\n "architecture": "x86_64",\n "id": "fixture-linux",\n "isolation": "container",\n "platform": "linux"\n },\n "evidence_id": "ev_39dc6ca57fd8d981c0bce63a1de75dd752e0ae47ff911440949cba8f6a8f1b7c",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "cleanup": {\n "owned_process_group": "verified",\n "temporary_root": "removed"\n },\n "event_journal": [],\n "exit": {\n "code": 0,\n "reason": "exited",\n "signal": null\n },\n "files_after": [],\n "files_before": [],\n "filesystem_checkpoints": [\n {\n "at_ms": 0,\n "effects": [],\n "files": [],\n "name": "before",\n "truncated": false\n },\n {\n "at_ms": 50,\n "effects": [],\n "files": [],\n "name": "after_settlement",\n "truncated": false\n }\n ],\n "filesystem_effects": [],\n "frames": [],\n "interaction_events": [],\n "limitations": [],\n "manifest": {\n "architecture": "fixture",\n "comparison_contract": {},\n "comparison_contract_sha256": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",\n "completed_at": "2026-01-01T00:00:00.001Z",\n "executable_sha256": "0000000000000000000000000000000000000000000000000000000000000000",\n "full_scenario_sha256": "e9fb6dbb12060cb724e2c6360da0d406b8dcb13ed91769105f92efe312491fc7",\n "normalization_sha256": "60a27b2d1ee5aa73bf2e5cb2deaed2e9cc548f961ad2e3772c981828b643cc43",\n "platform": "fixture",\n "provider_version": "3",\n "pty_backend": "node-pty",\n "rea_version": "1.1.0",\n "scenario": {\n "executable_sha256": "0000000000000000000000000000000000000000000000000000000000000000"\n },\n "started_at": "2026-01-01T00:00:00.000Z"\n },\n "normalization": {\n "paths": true,\n "patterns": [],\n "pids": true,\n "ports": true,\n "time_bucket_ms": 10\n },\n "process_samples": [],\n "rendered_frames": [],\n "residual_unknowns": [],\n "settlement": {\n "cleanup_outcome": "not_required",\n "elapsed_ms": 50,\n "state": "quiesced"\n },\n "truncated": false\n },\n "operation": "capture_process_scenario",\n "parameters": {\n "scenario": "reference"\n },\n "predicate_type": "rea.process-capture",\n "provider": {\n "id": "rea-process",\n "name": "REA deterministic process harness",\n "version": "3"\n },\n "raw_result": null,\n "subject": null\n },\n "right": {\n "authority": "controlled-replay",\n "confidence": "observed",\n "environment": {\n "architecture": "x86_64",\n "id": "fixture-linux",\n "isolation": "container",\n "platform": "linux"\n },\n "evidence_id": "ev_434bd49ec7096a6a955adb869d0f38ae3cb62f1256376ca87ca7ca983e5ef538",\n "evidence_links": [],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "cleanup": {\n "owned_process_group": "verified",\n "temporary_root": "removed"\n },\n "event_journal": [],\n "exit": {\n "code": 0,\n "reason": "exited",\n "signal": null\n },\n "files_after": [],\n "files_before": [],\n "filesystem_checkpoints": [\n {\n "at_ms": 0,\n "effects": [],\n "files": [],\n "name": "before",\n "truncated": false\n },\n {\n "at_ms": 50,\n "effects": [],\n "files": [],\n "name": "after_settlement",\n "truncated": false\n }\n ],\n "filesystem_effects": [],\n "frames": [],\n "interaction_events": [],\n "limitations": [],\n "manifest": {\n "architecture": "fixture",\n "comparison_contract": {},\n "comparison_contract_sha256": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",\n "completed_at": "2026-01-01T00:00:00.001Z",\n "executable_sha256": "0000000000000000000000000000000000000000000000000000000000000000",\n "full_scenario_sha256": "e9fb6dbb12060cb724e2c6360da0d406b8dcb13ed91769105f92efe312491fc7",\n "normalization_sha256": "60a27b2d1ee5aa73bf2e5cb2deaed2e9cc548f961ad2e3772c981828b643cc43",\n "platform": "fixture",\n "provider_version": "3",\n "pty_backend": "node-pty",\n "rea_version": "1.1.0",\n "scenario": {\n "executable_sha256": "0000000000000000000000000000000000000000000000000000000000000000"\n },\n "started_at": "2026-01-01T00:00:00.000Z"\n },\n "normalization": {\n "paths": true,\n "patterns": [],\n "pids": true,\n "ports": true,\n "time_bucket_ms": 10\n },\n "process_samples": [],\n "rendered_frames": [],\n "residual_unknowns": [],\n "settlement": {\n "cleanup_outcome": "not_required",\n "elapsed_ms": 50,\n "state": "quiesced"\n },\n "truncated": false\n },\n "operation": "capture_process_scenario",\n "parameters": {\n "scenario": "reconstruction"\n },\n "predicate_type": "rea.process-capture",\n "provider": {\n "id": "rea-process",\n "name": "REA deterministic process harness",\n "version": "3"\n },\n "raw_result": null,\n "subject": null\n }\n }\n ],\n "properties": {\n "left": {\n "additionalProperties": false,\n "description": "Left-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "max_capture_age_ms": {\n "description": "Maximum permitted capture age ms for this operation.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "right": {\n "additionalProperties": false,\n "description": "Right-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "trace_spec": {\n "additionalProperties": false,\n "description": "Value for trace spec.",\n "properties": {\n "events": {\n "description": "Value for events.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "cardinality": {\n "default": {\n "kind": "required"\n },\n "description": "Value for cardinality.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "required",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "optional",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "count": {\n "description": "Value for count.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "exact",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "count"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "range",\n "description": "Value for kind.",\n "type": "string"\n },\n "max": {\n "description": "Value for max.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "min": {\n "description": "Value for min.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "min",\n "max"\n ],\n "type": "object"\n }\n ]\n },\n "exact": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for exact."\n },\n "id": {\n "description": "Value for id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "ignore_fields": {\n "description": "Value for ignore fields.",\n "items": {\n "enum": [\n "sequence",\n "at_ms",\n "scheduled_at_ms",\n "dispatched_at_ms",\n "elapsed_ms"\n ],\n "type": "string"\n },\n "type": "array"\n },\n "source": {\n "description": "Value for source.",\n "enum": [\n "terminal_raw",\n "terminal_rendered",\n "interaction",\n "lifecycle",\n "process",\n "filesystem"\n ],\n "type": "string"\n }\n },\n "required": [\n "id",\n "source",\n "exact"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "language": {\n "description": "Value for language.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "happens_before": {\n "default": [],\n "description": "Value for happens before.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "after": {\n "description": "The later or right-hand observation to compare.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "before": {\n "description": "The earlier or left-hand observation to compare.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n }\n },\n "required": [\n "before",\n "after"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "kind": {\n "const": "partial_order",\n "description": "Value for kind.",\n "type": "string"\n },\n "not_before": {\n "default": [],\n "description": "Value for not before.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "anchor": {\n "description": "Value for anchor.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "event": {\n "description": "Value for event.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n }\n },\n "required": [\n "event",\n "anchor"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "prefix": {\n "default": [],\n "description": "Value for prefix.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "suffix": {\n "default": [],\n "description": "Value for suffix.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "type": "array"\n },\n "unordered_groups": {\n "default": [],\n "description": "Value for unordered groups.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "events": {\n "description": "Value for events.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n }\n },\n "required": [\n "events"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "finite_traces",\n "description": "Value for kind.",\n "type": "string"\n },\n "variants": {\n "description": "Value for variants.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "description": "Value for id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "trace": {\n "description": "Value for trace.",\n "items": {\n "anyOf": [\n {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n {\n "additionalProperties": false,\n "properties": {\n "unordered": {\n "description": "Value for unordered.",\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n }\n },\n "required": [\n "unordered"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n }\n },\n "required": [\n "id",\n "trace"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "kind",\n "variants"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "events",\n "language"\n ],\n "type": "object"\n }\n },\n "required": [\n "left",\n "right"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "compare_process_captures",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "exit": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "filesystem": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "first_divergence": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "status": {\n "const": "none",\n "type": "string"\n }\n },\n "required": [\n "status"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "status",\n "reason"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimension": {\n "enum": [\n "terminal",\n "interaction",\n "exit",\n "filesystem",\n "process"\n ],\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "left": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_at_ms": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_at_ms": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "status": {\n "const": "found",\n "type": "string"\n }\n },\n "required": [\n "status",\n "dimension",\n "index",\n "left_at_ms",\n "right_at_ms",\n "left",\n "right"\n ],\n "type": "object"\n }\n ]\n },\n "interaction": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "process": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "terminal": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "trace": {\n "additionalProperties": false,\n "properties": {\n "diagnostic": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "event_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,63}$",\n "type": "string"\n },\n "type": "array"\n },\n "kind": {\n "enum": [\n "journal",\n "predicate",\n "cardinality",\n "edge",\n "prefix",\n "suffix",\n "trace"\n ],\n "type": "string"\n },\n "locations": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "capture_order": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "collection": {\n "enum": [\n "frames",\n "rendered_frames",\n "interaction_events",\n "lifecycle",\n "process_samples",\n "filesystem_checkpoints"\n ],\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "collection",\n "index",\n "capture_order"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "message": {\n "type": "string"\n },\n "side": {\n "enum": [\n "left",\n "right"\n ],\n "type": "string"\n }\n },\n "required": [\n "kind",\n "side",\n "message",\n "event_ids",\n "locations"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "left": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "matched_variant": {\n "anyOf": [\n {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,63}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "raw_trace": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "event_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,63}$",\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "capture_order": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "collection": {\n "enum": [\n "frames",\n "rendered_frames",\n "interaction_events",\n "lifecycle",\n "process_samples",\n "filesystem_checkpoints"\n ],\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "collection",\n "index",\n "capture_order"\n ],\n "type": "object"\n }\n },\n "required": [\n "event_id",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "satisfied_constraints": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "pass",\n "type": "string"\n }\n },\n "required": [\n "satisfied_constraints",\n "raw_trace",\n "status",\n "matched_variant"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "matched_variant": {\n "type": "null"\n },\n "raw_trace": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "event_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,63}$",\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "capture_order": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "collection": {\n "enum": [\n "frames",\n "rendered_frames",\n "interaction_events",\n "lifecycle",\n "process_samples",\n "filesystem_checkpoints"\n ],\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "collection",\n "index",\n "capture_order"\n ],\n "type": "object"\n }\n },\n "required": [\n "event_id",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "satisfied_constraints": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "fail",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "satisfied_constraints",\n "raw_trace",\n "status",\n "matched_variant"\n ],\n "type": "object"\n }\n ]\n },\n "right": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "matched_variant": {\n "anyOf": [\n {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,63}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "raw_trace": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "event_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,63}$",\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "capture_order": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "collection": {\n "enum": [\n "frames",\n "rendered_frames",\n "interaction_events",\n "lifecycle",\n "process_samples",\n "filesystem_checkpoints"\n ],\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "collection",\n "index",\n "capture_order"\n ],\n "type": "object"\n }\n },\n "required": [\n "event_id",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "satisfied_constraints": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "const": "pass",\n "type": "string"\n }\n },\n "required": [\n "satisfied_constraints",\n "raw_trace",\n "status",\n "matched_variant"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "matched_variant": {\n "type": "null"\n },\n "raw_trace": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "event_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,63}$",\n "type": "string"\n },\n "location": {\n "additionalProperties": false,\n "properties": {\n "capture_order": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "collection": {\n "enum": [\n "frames",\n "rendered_frames",\n "interaction_events",\n "lifecycle",\n "process_samples",\n "filesystem_checkpoints"\n ],\n "type": "string"\n },\n "index": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "collection",\n "index",\n "capture_order"\n ],\n "type": "object"\n }\n },\n "required": [\n "event_id",\n "location"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "satisfied_constraints": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "fail",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "satisfied_constraints",\n "raw_trace",\n "status",\n "matched_variant"\n ],\n "type": "object"\n }\n ]\n },\n "verdict": {\n "enum": [\n "equivalent",\n "different",\n "nonconforming",\n "unknown"\n ],\n "type": "string"\n }\n },\n "required": [\n "verdict",\n "left",\n "right",\n "diagnostic"\n ],\n "type": "object"\n }\n },\n "required": [\n "status",\n "terminal",\n "interaction",\n "exit",\n "filesystem",\n "process",\n "first_divergence",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Compare Process Captures"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare complete artifact inventories by logical occurrence path, content identity, metadata, and graph relations. Pass the inventory Evidence nested in each inspect_artifact result as left and right. Every delta cites both inputs, and gaps yield truncated or unknown, never equivalence. Returns every change inline.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "left": {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_196e5dd37264f7b088ab8c0be326d22cac37e798bc89c72bcabc2ef9d74eed55",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "edges": [],\n "limitations": [],\n "manifest": {\n "edge_count": 0,\n "graph_sha256": "8fa9468fe1a1a23c968cc1215eac19cce2510530cb8cbdd99b0b93bcd251c586",\n "manifest_id": "agm_aada25a1123169497307a2f381969ca213532b63d2259e999bffe2aa50fe5227",\n "node_count": 1,\n "occurrence_count": 1,\n "root_artifact_id": "art_2827b82d5d864b615b237ca45cf8a10fc5c14a1b73ba5bb8b358dddefc8e9f8c",\n "root_format": "file",\n "root_sha256": "0000000000000000000000000000000000000000000000000000000000000000"\n },\n "nodes": [\n {\n "architecture": null,\n "artifact_id": "art_2827b82d5d864b615b237ca45cf8a10fc5c14a1b73ba5bb8b358dddefc8e9f8c",\n "content_state": "materialized",\n "executable": false,\n "format": "file",\n "kind": "resource",\n "limitations": [],\n "media_type": null,\n "sha256": "0000000000000000000000000000000000000000000000000000000000000000",\n "size": 1\n }\n ],\n "occurrences": [\n {\n "artifact_id": "art_2827b82d5d864b615b237ca45cf8a10fc5c14a1b73ba5bb8b358dddefc8e9f8c",\n "compressed_size": null,\n "declared_size": 1,\n "encrypted": false,\n "entry_kind": "file",\n "executable": false,\n "hash_status": "verified",\n "limitations": [],\n "logical_path": ".",\n "occurrence_id": "occ_273bef4da5057c50b27edf034ffce68d5d9c0c5795778fc3ff41e4e6b3ef5586",\n "parent_occurrence_id": null,\n "source_location": null\n }\n ],\n "provenance": []\n },\n "operation": "inventory_artifact",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-artifact",\n "name": "REA artifact graph",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "0000000000000000000000000000000000000000000000000000000000000000"\n },\n "format": "file",\n "local_path": "fixture-left",\n "name": "fixture-left"\n }\n },\n "right": {\n "authority": "shipped-artifact",\n "confidence": "observed",\n "environment": null,\n "evidence_id": "ev_8c9ecce090f574469d379fb8bb173e65eff08071fa599b37547ed32dcd102a09",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "edges": [],\n "limitations": [],\n "manifest": {\n "edge_count": 0,\n "graph_sha256": "6e02ceaea80d013e00e4c0c30621df60312f00e03dc338ec47f548671f6a7ec4",\n "manifest_id": "agm_6fa9493e8b0d8164b3ca317323aeffc6be1f81e1e7a3cf4e668310093ba8ef73",\n "node_count": 1,\n "occurrence_count": 1,\n "root_artifact_id": "art_96f554e44817506903722ba66b6b001897989fe181ef986478bc023e1fedf4b1",\n "root_format": "file",\n "root_sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "nodes": [\n {\n "architecture": null,\n "artifact_id": "art_96f554e44817506903722ba66b6b001897989fe181ef986478bc023e1fedf4b1",\n "content_state": "materialized",\n "executable": false,\n "format": "file",\n "kind": "resource",\n "limitations": [],\n "media_type": null,\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "size": 1\n }\n ],\n "occurrences": [\n {\n "artifact_id": "art_96f554e44817506903722ba66b6b001897989fe181ef986478bc023e1fedf4b1",\n "compressed_size": null,\n "declared_size": 1,\n "encrypted": false,\n "entry_kind": "file",\n "executable": false,\n "hash_status": "verified",\n "limitations": [],\n "logical_path": ".",\n "occurrence_id": "occ_4257a159555da426a3dd9e420ba59795ef3d87d0b1fd93e5cc7d969539519364",\n "parent_occurrence_id": null,\n "source_location": null\n }\n ],\n "provenance": []\n },\n "operation": "inventory_artifact",\n "parameters": {},\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-artifact",\n "name": "REA artifact graph",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "file",\n "local_path": "fixture-right",\n "name": "fixture-right"\n }\n }\n }\n ],\n "properties": {\n "left": {\n "additionalProperties": false,\n "description": "Left-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "description": "Right-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "left",\n "right"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "compare_artifacts",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "classification": {\n "enum": [\n "added",\n "removed",\n "changed",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "dimensions": {\n "items": {\n "enum": [\n "content",\n "kind",\n "format",\n "size",\n "executable",\n "relations",\n "metadata",\n "availability",\n "integrity"\n ],\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "left_artifact_id": {\n "anyOf": [\n {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_occurrence_id": {\n "anyOf": [\n {\n "pattern": "^occ_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "logical_path": {\n "minLength": 1,\n "type": "string"\n },\n "right_artifact_id": {\n "anyOf": [\n {\n "pattern": "^art_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_occurrence_id": {\n "anyOf": [\n {\n "pattern": "^occ_[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "classification",\n "logical_path",\n "dimensions",\n "left_occurrence_id",\n "right_occurrence_id",\n "left_artifact_id",\n "right_artifact_id",\n "evidence_links"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "left_manifest_id": {\n "pattern": "^agm_[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_manifest_id": {\n "pattern": "^agm_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "added": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "changed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "contradiction": {\n "default": 0,\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unchanged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "unknown",\n "contradiction"\n ],\n "type": "object"\n }\n },\n "required": [\n "status",\n "left_manifest_id",\n "right_manifest_id",\n "summary",\n "changes",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Compare Artifacts"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare two explicit sets of analyze_function Evidence across identity, exact provider text, calls, references, strings, and address-normalized CFG topology. Missing or provider-incompatible facets remain truncated or unknown; every conclusion cites both Evidence sets.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "left": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "assembly": [],\n "basic_blocks": [\n {\n "end": "0x1001",\n "start": "0x1000",\n "successors": []\n }\n ],\n "callees": [],\n "callers": [],\n "comments": [],\n "incoming_references": [],\n "outgoing_references": [],\n "procedure": {\n "address": "0x1000",\n "locals": [],\n "name": "main",\n "signature": "int main(void)"\n },\n "pseudocode": "return 0;",\n "referenced_names": [],\n "referenced_strings": []\n },\n "operation": "analyze_function",\n "parameters": {\n "procedure": "main"\n },\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-workflow",\n "name": "REA composed investigation workflow",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "0000000000000000000000000000000000000000000000000000000000000000"\n },\n "format": "mach-o",\n "local_path": "/tmp/function-0",\n "name": "function-0"\n }\n },\n "right": {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "assembly": [],\n "basic_blocks": [\n {\n "end": "0x1001",\n "start": "0x1000",\n "successors": []\n }\n ],\n "callees": [],\n "callers": [],\n "comments": [],\n "incoming_references": [],\n "outgoing_references": [],\n "procedure": {\n "address": "0x1000",\n "locals": [],\n "name": "main",\n "signature": "int main(void)"\n },\n "pseudocode": "return 1;",\n "referenced_names": [],\n "referenced_strings": []\n },\n "operation": "analyze_function",\n "parameters": {\n "procedure": "main"\n },\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-workflow",\n "name": "REA composed investigation workflow",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "1111111111111111111111111111111111111111111111111111111111111111"\n },\n "format": "mach-o",\n "local_path": "/tmp/function-1",\n "name": "function-1"\n }\n }\n }\n ],\n "properties": {\n "left": {\n "additionalProperties": false,\n "description": "Left-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "right": {\n "additionalProperties": false,\n "description": "Right-hand input used for comparison or differential execution.",\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n },\n "required": [\n "left",\n "right"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "compare_functions",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "conclusion_kind": {\n "const": "derived_relationship",\n "type": "string"\n },\n "dimension": {\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "left_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "text_delta": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "added_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "hunks": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "added_lines",\n "removed_lines",\n "hunks"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "dimension",\n "left_count",\n "right_count",\n "evidence_links",\n "limitations",\n "status",\n "left_digest",\n "right_digest",\n "text_delta",\n "conclusion_kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "conclusion_kind": {\n "enum": [\n "derived_relationship",\n "contradiction"\n ],\n "type": "string"\n },\n "dimension": {\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "left_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "text_delta": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "added_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "hunks": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "added_lines",\n "removed_lines",\n "hunks"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "dimension",\n "left_count",\n "right_count",\n "evidence_links",\n "limitations",\n "status",\n "left_digest",\n "right_digest",\n "text_delta",\n "conclusion_kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "conclusion_kind": {\n "const": "unresolved_branch",\n "type": "string"\n },\n "dimension": {\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "left_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_digest": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_digest": {\n "type": "null"\n },\n "status": {\n "enum": [\n "truncated",\n "unknown"\n ],\n "type": "string"\n },\n "text_delta": {\n "type": "null"\n }\n },\n "required": [\n "dimension",\n "left_count",\n "right_count",\n "evidence_links",\n "limitations",\n "status",\n "left_digest",\n "right_digest",\n "text_delta",\n "conclusion_kind"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "dimensions": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "conclusion_kind": {\n "const": "derived_relationship",\n "type": "string"\n },\n "dimension": {\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "left_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "unchanged",\n "type": "string"\n },\n "text_delta": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "added_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "hunks": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "added_lines",\n "removed_lines",\n "hunks"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "dimension",\n "left_count",\n "right_count",\n "evidence_links",\n "limitations",\n "status",\n "left_digest",\n "right_digest",\n "text_delta",\n "conclusion_kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "conclusion_kind": {\n "enum": [\n "derived_relationship",\n "contradiction"\n ],\n "type": "string"\n },\n "dimension": {\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "left_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "const": "changed",\n "type": "string"\n },\n "text_delta": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "added_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "hunks": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "removed_lines": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "added_lines",\n "removed_lines",\n "hunks"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "dimension",\n "left_count",\n "right_count",\n "evidence_links",\n "limitations",\n "status",\n "left_digest",\n "right_digest",\n "text_delta",\n "conclusion_kind"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "conclusion_kind": {\n "const": "unresolved_branch",\n "type": "string"\n },\n "dimension": {\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "left_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_digest": {\n "type": "null"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_count": {\n "anyOf": [\n {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_digest": {\n "type": "null"\n },\n "status": {\n "enum": [\n "truncated",\n "unknown"\n ],\n "type": "string"\n },\n "text_delta": {\n "type": "null"\n }\n },\n "required": [\n "dimension",\n "left_count",\n "right_count",\n "evidence_links",\n "limitations",\n "status",\n "left_digest",\n "right_digest",\n "text_delta",\n "conclusion_kind"\n ],\n "type": "object"\n }\n ]\n },\n "maxItems": 8,\n "minItems": 8,\n "type": "array"\n },\n "function_match": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "left_name": {\n "type": "string"\n },\n "method": {\n "const": "symbol",\n "type": "string"\n },\n "right_name": {\n "type": "string"\n },\n "status": {\n "const": "matched",\n "type": "string"\n }\n },\n "required": [\n "status",\n "method",\n "left_name",\n "right_name"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "left_name": {\n "type": "string"\n },\n "method": {\n "const": "explicit",\n "type": "string"\n },\n "right_name": {\n "type": "string"\n },\n "status": {\n "enum": [\n "mismatched",\n "ambiguous"\n ],\n "type": "string"\n }\n },\n "required": [\n "status",\n "method",\n "left_name",\n "right_name"\n ],\n "type": "object"\n }\n ]\n },\n "left_subject_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_subject_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "unchanged",\n "changed",\n "truncated",\n "unknown"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "changed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "truncated": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unchanged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "unchanged",\n "changed",\n "truncated",\n "unknown"\n ],\n "type": "object"\n }\n },\n "required": [\n "status",\n "function_match",\n "left_subject_sha256",\n "right_subject_sha256",\n "summary",\n "dimensions",\n "changes",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Compare Functions"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Compare two canonical Evidence bundles by exact record membership, explicit one-to-one observation pairs, and complete residual-unknown revision histories. Missing bundle members describe omission only, never behavioral equivalence; output is digest-anchored and returns every change inline.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "left_bundle_path": "/tmp/left-evidence.json",\n "record_pairs": [],\n "right_bundle_path": "/tmp/right-evidence.json"\n }\n ],\n "properties": {\n "left_bundle_path": {\n "description": "Local filesystem path for left bundle.",\n "minLength": 1,\n "type": "string"\n },\n "record_pairs": {\n "default": [],\n "description": "Value for record pairs.",\n "items": {\n "properties": {\n "left_evidence_id": {\n "description": "Exact Evidence identifier for the left observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "right_evidence_id": {\n "description": "Exact Evidence identifier for the right observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "left_evidence_id",\n "right_evidence_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "right_bundle_path": {\n "description": "Local filesystem path for right bundle.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "left_bundle_path",\n "right_bundle_path"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "compare_bundles",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "changes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "classification": {\n "enum": [\n "added",\n "removed",\n "changed",\n "unknown",\n "history_advanced",\n "history_diverged"\n ],\n "type": "string"\n },\n "conclusion_kind": {\n "enum": [\n "observed_change",\n "contradiction",\n "unresolved_branch"\n ],\n "type": "string"\n },\n "entity": {\n "enum": [\n "evidence",\n "residual_unknown"\n ],\n "type": "string"\n },\n "key": {\n "minLength": 1,\n "type": "string"\n },\n "left_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "left_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "right_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "entity",\n "key",\n "classification",\n "conclusion_kind",\n "left_evidence_ids",\n "right_evidence_ids",\n "left_digest",\n "right_digest",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "left_bundle_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "right_bundle_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "unchanged",\n "changed",\n "unknown"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "records_added": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "records_changed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "records_removed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "records_unchanged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknowns_added": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknowns_advanced": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknowns_diverged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknowns_removed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unknowns_unchanged": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unresolved": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "records_unchanged",\n "records_added",\n "records_removed",\n "records_changed",\n "unknowns_unchanged",\n "unknowns_added",\n "unknowns_removed",\n "unknowns_advanced",\n "unknowns_diverged",\n "unresolved"\n ],\n "type": "object"\n }\n },\n "required": [\n "status",\n "left_bundle_sha256",\n "right_bundle_sha256",\n "summary",\n "changes",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Compare Bundles"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Aggregate validated process and artifact comparison Evidence. Runtime observations remain distinct from static behavior candidates; missing or incomplete comparisons produce unresolved findings, never causal claims. Returns every finding inline.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "comparisons": [\n {\n "authority": "analyst-inference",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_cc8049f629a9318e985bee4a547c0ec6646e9d3c2cbbabf4ac9b3758bc3ede86",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "changes": [\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "pseudocode",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 9,\n "left_digest": "64abdfe133a114158fa0e185d0edec784e6ccf13a166a0fed8bc18bb0cc72554",\n "limitations": [],\n "right_count": 9,\n "right_digest": "c8e26d77640903b2d4c765ebcb7f7170b64dd320dd3911abbe8346e07cd593d1",\n "status": "changed",\n "text_delta": {\n "added_lines": 1,\n "hunks": 2,\n "removed_lines": 1\n }\n }\n ],\n "dimensions": [\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "identity",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": null,\n "left_digest": "dd190a914de1bdf8f4bba97a2e74b439585874c567f28eb0ff41bc5388c66d65",\n "limitations": [],\n "right_count": null,\n "right_digest": "dd190a914de1bdf8f4bba97a2e74b439585874c567f28eb0ff41bc5388c66d65",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "pseudocode",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 9,\n "left_digest": "64abdfe133a114158fa0e185d0edec784e6ccf13a166a0fed8bc18bb0cc72554",\n "limitations": [],\n "right_count": 9,\n "right_digest": "c8e26d77640903b2d4c765ebcb7f7170b64dd320dd3911abbe8346e07cd593d1",\n "status": "changed",\n "text_delta": {\n "added_lines": 1,\n "hunks": 2,\n "removed_lines": 1\n }\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "assembly",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",\n "limitations": [\n "Assembly is opaque provider text; relocation normalization is unavailable."\n ],\n "right_count": 0,\n "right_digest": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",\n "status": "unchanged",\n "text_delta": {\n "added_lines": 0,\n "hunks": 0,\n "removed_lines": 0\n }\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "comments",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "calls",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "references",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "strings_names",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "cfg",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 1,\n "left_digest": "e947121f0d4cb8d2a2468a75a44872a29aa1239e6401712df80cfa36bf7df7a0",\n "limitations": [],\n "right_count": 1,\n "right_digest": "e947121f0d4cb8d2a2468a75a44872a29aa1239e6401712df80cfa36bf7df7a0",\n "status": "unchanged",\n "text_delta": null\n }\n ],\n "function_match": {\n "left_name": "main",\n "method": "symbol",\n "right_name": "main",\n "status": "matched"\n },\n "left_subject_sha256": "0000000000000000000000000000000000000000000000000000000000000000",\n "limitations": [],\n "right_subject_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "status": "changed",\n "summary": {\n "changed": 1,\n "truncated": 0,\n "unchanged": 7,\n "unknown": 0\n }\n },\n "operation": "compare_functions",\n "parameters": {\n "left_evidence_id": "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "right_evidence_id": "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n },\n "predicate_type": "rea.function-comparison",\n "provider": {\n "id": "rea-function-comparison",\n "name": "REA function comparison",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n }\n ]\n }\n ],\n "properties": {\n "comparisons": {\n "default": [],\n "description": "Validated comparison Evidence records to aggregate.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "type": "object"\n },\n "kind": "session",\n "name": "find_changed_behavior",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "behavior_status": {\n "enum": [\n "observed_changed",\n "observed_unchanged",\n "unknown",\n "truncated"\n ],\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 3,\n "type": "array"\n },\n "findings": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "classification": {\n "enum": [\n "observed_change",\n "derived_relationship",\n "contradiction",\n "unresolved_branch"\n ],\n "type": "string"\n },\n "dimension": {\n "minLength": 1,\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 3,\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "scope": {\n "enum": [\n "runtime",\n "protocol",\n "resource",\n "static_candidate"\n ],\n "type": "string"\n },\n "source_comparison_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n }\n },\n "required": [\n "scope",\n "dimension",\n "classification",\n "status",\n "source_comparison_id",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "items"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "contradictions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "observed_changes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "static_candidates": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unresolved": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "observed_changes",\n "static_candidates",\n "contradictions",\n "unresolved"\n ],\n "type": "object"\n }\n },\n "required": [\n "behavior_status",\n "summary",\n "findings",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Find Changed Behavior"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Build every shortest direct-callee path inline from complete analyze_function Evidence records using exact canonical addresses. Missing dossiers and provider mixing remain unknown; every node and edge cites source Evidence.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "functions": [\n {\n "authority": "shipped-artifact",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "evidence_links": [],\n "limitations": [],\n "locations": [],\n "normalized_result": {\n "assembly": [],\n "basic_blocks": [\n {\n "end": "0x1001",\n "start": "0x1000",\n "successors": []\n }\n ],\n "callees": [],\n "callers": [],\n "comments": [],\n "incoming_references": [],\n "outgoing_references": [],\n "procedure": {\n "address": "0x1000",\n "locals": [],\n "name": "main",\n "signature": "int main(void)"\n },\n "pseudocode": "return 0;",\n "referenced_names": [],\n "referenced_strings": []\n },\n "operation": "analyze_function",\n "parameters": {\n "procedure": "main"\n },\n "predicate_type": "rea.analysis",\n "provider": {\n "id": "rea-workflow",\n "name": "REA composed investigation workflow",\n "version": "1"\n },\n "raw_result": null,\n "subject": {\n "architecture": null,\n "digest": {\n "sha256": "0000000000000000000000000000000000000000000000000000000000000000"\n },\n "format": "mach-o",\n "local_path": "/tmp/function-0",\n "name": "function-0"\n }\n }\n ],\n "goal": {\n "address": "0x1000"\n },\n "start": {\n "address": "0x1000"\n }\n }\n ],\n "properties": {\n "functions": {\n "description": "Value for functions.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "goal": {\n "additionalProperties": false,\n "description": "Value for goal.",\n "properties": {\n "address": {\n "description": "Value for address.",\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9a-fA-F]{2})+:)?0[xX][0-9a-fA-F]+$",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "start": {\n "additionalProperties": false,\n "description": "Value for start.",\n "properties": {\n "address": {\n "description": "Value for address.",\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9a-fA-F]{2})+:)?0[xX][0-9a-fA-F]+$",\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n }\n },\n "required": [\n "functions",\n "start",\n "goal"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "build_call_path",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "explored": {\n "additionalProperties": false,\n "properties": {\n "depth_reached": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "nodes",\n "edges",\n "depth_reached"\n ],\n "type": "object"\n },\n "goal": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "paths": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "edges": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "source": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "target": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n }\n },\n "required": [\n "source",\n "target",\n "evidence_links"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "hops": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nodes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "address",\n "name",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "hops",\n "nodes",\n "edges",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "search_scope": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "type": "boolean"\n }\n },\n "required": [\n "exhaustive"\n ],\n "type": "object"\n },\n "shortest_hops": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "start": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "status": {\n "const": "found",\n "type": "string"\n }\n },\n "required": [\n "start",\n "goal",\n "explored",\n "evidence_links",\n "limitations",\n "status",\n "shortest_hops",\n "search_scope",\n "paths"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "explored": {\n "additionalProperties": false,\n "properties": {\n "depth_reached": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "nodes",\n "edges",\n "depth_reached"\n ],\n "type": "object"\n },\n "goal": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "paths": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "search_scope": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "const": true,\n "type": "boolean"\n }\n },\n "required": [\n "exhaustive"\n ],\n "type": "object"\n },\n "shortest_hops": {\n "type": "null"\n },\n "start": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "status": {\n "const": "not_found",\n "type": "string"\n }\n },\n "required": [\n "start",\n "goal",\n "explored",\n "evidence_links",\n "limitations",\n "status",\n "shortest_hops",\n "search_scope",\n "paths"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "explored": {\n "additionalProperties": false,\n "properties": {\n "depth_reached": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "edges": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "nodes": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "nodes",\n "edges",\n "depth_reached"\n ],\n "type": "object"\n },\n "goal": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "paths": {\n "items": {\n "not": {}\n },\n "maxItems": 0,\n "minItems": 0,\n "type": "array"\n },\n "search_scope": {\n "additionalProperties": false,\n "properties": {\n "exhaustive": {\n "const": false,\n "type": "boolean"\n }\n },\n "required": [\n "exhaustive"\n ],\n "type": "object"\n },\n "shortest_hops": {\n "type": "null"\n },\n "start": {\n "pattern": "^(?:(?:[A-Za-z0-9._~-]|%[0-9A-F]{2})+:)?0x(?:0|[1-9a-f][0-9a-f]*)$",\n "type": "string"\n },\n "status": {\n "const": "unknown",\n "type": "string"\n }\n },\n "required": [\n "start",\n "goal",\n "explored",\n "evidence_links",\n "limitations",\n "status",\n "shortest_hops",\n "search_scope",\n "paths"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Build Call Path"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Evaluate every explicit caller-declared hypothesis between exact static comparison findings and runtime comparison dimensions. Similar names or paths are never auto-matched, consistent cochange never proves causality, and unknown or truncated inputs remain unresolved. Returns all correlations and their complete Evidence closure inline.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "mappings": [\n {\n "hypothesis": {\n "expected_pattern": "static_only",\n "statement": "Static implementation changed without terminal change."\n },\n "runtime": {\n "comparison_evidence_id": "ev_7b90781a4b4c9006d4eee122009b3baed342e442ad2ad62498bf9e7df850355c",\n "dimension": "terminal"\n },\n "side_alignment": "left_to_left",\n "static": {\n "comparison_evidence_id": "ev_cc8049f629a9318e985bee4a547c0ec6646e9d3c2cbbabf4ac9b3758bc3ede86",\n "selector": {\n "dimension": "pseudocode",\n "kind": "function"\n }\n }\n }\n ],\n "runtime_comparisons": [\n {\n "authority": "analyst-inference",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_7b90781a4b4c9006d4eee122009b3baed342e442ad2ad62498bf9e7df850355c",\n "evidence_links": [\n "ev_39dc6ca57fd8d981c0bce63a1de75dd752e0ae47ff911440949cba8f6a8f1b7c",\n "ev_434bd49ec7096a6a955adb869d0f38ae3cb62f1256376ca87ca7ca983e5ef538"\n ],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "exit": "unchanged",\n "filesystem": "unchanged",\n "first_divergence": {\n "status": "none"\n },\n "interaction": "unchanged",\n "limitations": [],\n "process": "unchanged",\n "status": "unchanged",\n "terminal": "unchanged"\n },\n "operation": "compare_process_captures",\n "parameters": {\n "left_evidence_id": "ev_39dc6ca57fd8d981c0bce63a1de75dd752e0ae47ff911440949cba8f6a8f1b7c",\n "left_normalization": {\n "paths": true,\n "patterns": [],\n "pids": true,\n "ports": true,\n "time_bucket_ms": 10\n },\n "right_evidence_id": "ev_434bd49ec7096a6a955adb869d0f38ae3cb62f1256376ca87ca7ca983e5ef538",\n "right_normalization": {\n "paths": true,\n "patterns": [],\n "pids": true,\n "ports": true,\n "time_bucket_ms": 10\n }\n },\n "predicate_type": "rea.process-comparison",\n "provider": {\n "id": "rea-process",\n "name": "REA deterministic process harness",\n "version": "3"\n },\n "raw_result": null,\n "subject": null\n }\n ],\n "static_comparisons": [\n {\n "authority": "analyst-inference",\n "confidence": "derived",\n "environment": null,\n "evidence_id": "ev_cc8049f629a9318e985bee4a547c0ec6646e9d3c2cbbabf4ac9b3758bc3ede86",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "limitations": [\n "Artifact identity is unavailable for this observation."\n ],\n "locations": [],\n "normalized_result": {\n "changes": [\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "pseudocode",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 9,\n "left_digest": "64abdfe133a114158fa0e185d0edec784e6ccf13a166a0fed8bc18bb0cc72554",\n "limitations": [],\n "right_count": 9,\n "right_digest": "c8e26d77640903b2d4c765ebcb7f7170b64dd320dd3911abbe8346e07cd593d1",\n "status": "changed",\n "text_delta": {\n "added_lines": 1,\n "hunks": 2,\n "removed_lines": 1\n }\n }\n ],\n "dimensions": [\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "identity",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": null,\n "left_digest": "dd190a914de1bdf8f4bba97a2e74b439585874c567f28eb0ff41bc5388c66d65",\n "limitations": [],\n "right_count": null,\n "right_digest": "dd190a914de1bdf8f4bba97a2e74b439585874c567f28eb0ff41bc5388c66d65",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "pseudocode",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 9,\n "left_digest": "64abdfe133a114158fa0e185d0edec784e6ccf13a166a0fed8bc18bb0cc72554",\n "limitations": [],\n "right_count": 9,\n "right_digest": "c8e26d77640903b2d4c765ebcb7f7170b64dd320dd3911abbe8346e07cd593d1",\n "status": "changed",\n "text_delta": {\n "added_lines": 1,\n "hunks": 2,\n "removed_lines": 1\n }\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "assembly",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",\n "limitations": [\n "Assembly is opaque provider text; relocation normalization is unavailable."\n ],\n "right_count": 0,\n "right_digest": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",\n "status": "unchanged",\n "text_delta": {\n "added_lines": 0,\n "hunks": 0,\n "removed_lines": 0\n }\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "comments",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "calls",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "references",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "strings_names",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 0,\n "left_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "limitations": [],\n "right_count": 0,\n "right_digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",\n "status": "unchanged",\n "text_delta": null\n },\n {\n "conclusion_kind": "derived_relationship",\n "dimension": "cfg",\n "evidence_links": [\n "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n ],\n "left_count": 1,\n "left_digest": "e947121f0d4cb8d2a2468a75a44872a29aa1239e6401712df80cfa36bf7df7a0",\n "limitations": [],\n "right_count": 1,\n "right_digest": "e947121f0d4cb8d2a2468a75a44872a29aa1239e6401712df80cfa36bf7df7a0",\n "status": "unchanged",\n "text_delta": null\n }\n ],\n "function_match": {\n "left_name": "main",\n "method": "symbol",\n "right_name": "main",\n "status": "matched"\n },\n "left_subject_sha256": "0000000000000000000000000000000000000000000000000000000000000000",\n "limitations": [],\n "right_subject_sha256": "1111111111111111111111111111111111111111111111111111111111111111",\n "status": "changed",\n "summary": {\n "changed": 1,\n "truncated": 0,\n "unchanged": 7,\n "unknown": 0\n }\n },\n "operation": "compare_functions",\n "parameters": {\n "left_evidence_id": "ev_fd7f0f3851d21e5284b47129d9c7142e2ebeb6ed1c5f91bcff18a781d0b24cb7",\n "right_evidence_id": "ev_79f87eddd8df1d59c6f2e2cd99f21bfae2e5a22fd26c5bf0d0b9ccf9e24b0522"\n },\n "predicate_type": "rea.function-comparison",\n "provider": {\n "id": "rea-function-comparison",\n "name": "REA function comparison",\n "version": "1"\n },\n "raw_result": null,\n "subject": null\n }\n ]\n }\n ],\n "properties": {\n "mappings": {\n "description": "Value for mappings.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "hypothesis": {\n "additionalProperties": false,\n "description": "Value for hypothesis.",\n "properties": {\n "expected_pattern": {\n "description": "Expected pattern used to reject stale or mismatched input.",\n "enum": [\n "cochanged",\n "static_only",\n "runtime_only",\n "both_unchanged"\n ],\n "type": "string"\n },\n "statement": {\n "description": "Value for statement.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "statement",\n "expected_pattern"\n ],\n "type": "object"\n },\n "runtime": {\n "additionalProperties": false,\n "description": "Value for runtime.",\n "properties": {\n "comparison_evidence_id": {\n "description": "Exact Evidence identifier for the comparison observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "dimension": {\n "description": "Value for dimension.",\n "enum": [\n "terminal",\n "interaction",\n "exit",\n "filesystem",\n "process"\n ],\n "type": "string"\n }\n },\n "required": [\n "comparison_evidence_id",\n "dimension"\n ],\n "type": "object"\n },\n "side_alignment": {\n "description": "Value for side alignment.",\n "enum": [\n "left_to_left",\n "left_to_right"\n ],\n "type": "string"\n },\n "static": {\n "additionalProperties": false,\n "description": "Value for static.",\n "properties": {\n "comparison_evidence_id": {\n "description": "Exact Evidence identifier for the comparison observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "selector": {\n "description": "Value for selector.",\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact",\n "description": "Value for kind.",\n "type": "string"\n },\n "logical_path": {\n "description": "Local filesystem path for logical.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "logical_path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimension": {\n "description": "Value for dimension.",\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "kind": {\n "const": "function",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "dimension"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "comparison_evidence_id",\n "selector"\n ],\n "type": "object"\n }\n },\n "required": [\n "static",\n "runtime",\n "side_alignment",\n "hypothesis"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "runtime_comparisons": {\n "description": "Value for runtime comparisons.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n },\n "static_comparisons": {\n "description": "Value for static comparisons.",\n "items": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "description": "Value for analysis profile.",\n "properties": {\n "digest": {\n "description": "Value for digest.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "description": "Value for version.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "description": "Value for authority.",\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "description": "Value for confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "description": "Value for architecture.",\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "description": "Value for isolation.",\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "description": "Value for platform.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for environment."\n },\n "evidence_id": {\n "description": "Stable identifier of the recorded Evidence observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "description": "Value for evidence links.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "description": "Value for limitations.",\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "description": "Value for locations.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "description": "Value for kind.",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "artifact-path",\n "description": "Value for kind.",\n "type": "string"\n },\n "path": {\n "description": "Local filesystem path used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "properties": {\n "kind": {\n "const": "file-offset",\n "description": "Value for kind.",\n "type": "string"\n },\n "offset": {\n "description": "Value for offset.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "properties": {\n "end": {\n "description": "Value for end.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "description": "Value for kind.",\n "type": "string"\n },\n "start": {\n "description": "Value for start.",\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0",\n "description": "Value for normalized result."\n },\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "description": "Value for parameters.",\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "description": "Value for predicate type.",\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "description": "Value for provider.",\n "properties": {\n "id": {\n "description": "Value for id.",\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for version."\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for raw result."\n },\n "subject": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "digest": {\n "description": "Value for digest.",\n "properties": {\n "sha256": {\n "description": "Value for sha256.",\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "description": "Declared input artifact format.",\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "description": "Local filesystem path for local.",\n "type": "string"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for subject."\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "minItems": 1,\n "type": "array"\n }\n },\n "required": [\n "static_comparisons",\n "runtime_comparisons",\n "mappings"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "correlate_static_and_runtime",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "correlations": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "classification": {\n "enum": [\n "hypothesis",\n "contradiction",\n "unresolved_branch"\n ],\n "type": "string"\n },\n "correlation_id": {\n "pattern": "^cor_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "hypothesis": {\n "additionalProperties": false,\n "properties": {\n "expected_pattern": {\n "enum": [\n "cochanged",\n "static_only",\n "runtime_only",\n "both_unchanged"\n ],\n "type": "string"\n },\n "statement": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "statement",\n "expected_pattern"\n ],\n "type": "object"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "observed_pattern": {\n "enum": [\n "cochanged",\n "static_only",\n "runtime_only",\n "both_unchanged",\n "unknown",\n "truncated"\n ],\n "type": "string"\n },\n "runtime": {\n "additionalProperties": false,\n "properties": {\n "comparison_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "dimension": {\n "enum": [\n "terminal",\n "interaction",\n "exit",\n "filesystem",\n "process"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n }\n },\n "required": [\n "comparison_evidence_id",\n "dimension",\n "status"\n ],\n "type": "object"\n },\n "side_alignment": {\n "enum": [\n "left_to_left",\n "left_to_right"\n ],\n "type": "string"\n },\n "static": {\n "additionalProperties": false,\n "properties": {\n "comparison_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "conclusion_kind": {\n "enum": [\n "derived_relationship",\n "contradiction",\n "unresolved_branch"\n ],\n "type": "string"\n },\n "selector": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact",\n "type": "string"\n },\n "logical_path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "logical_path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "dimension": {\n "enum": [\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "kind": {\n "const": "function",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "dimension"\n ],\n "type": "object"\n }\n ]\n },\n "status": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n }\n },\n "required": [\n "comparison_evidence_id",\n "selector",\n "status",\n "conclusion_kind"\n ],\n "type": "object"\n }\n },\n "required": [\n "correlation_id",\n "static",\n "runtime",\n "side_alignment",\n "hypothesis",\n "observed_pattern",\n "classification",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "items"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 2,\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "status": {\n "enum": [\n "correlated",\n "contradicted",\n "unknown",\n "truncated"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "contradictions": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "hypotheses": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "unresolved": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "hypotheses",\n "contradictions",\n "unresolved"\n ],\n "type": "object"\n }\n },\n "required": [\n "status",\n "summary",\n "correlations",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Correlate Static And Runtime"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Verify a finite typed behavioral and structural specification against a canonical Evidence bundle. Pass means every declared claim has complete comparable authority—not global source equivalence; changed claims fail and missing, limited, or unresolved evidence stays unknown.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "specification": {\n "claims": [\n {\n "claim_id": "terminal-output",\n "comparison_evidence_id": "ev_7b90781a4b4c9006d4eee122009b3baed342e442ad2ad62498bf9e7df850355c",\n "dimension": "terminal",\n "kind": "behavioral",\n "title": "Terminal output remains equivalent"\n }\n ],\n "name": "Terminal compatibility"\n }\n }\n ],\n "properties": {\n "specification": {\n "description": "Value for specification.",\n "properties": {\n "claims": {\n "description": "Value for claims.",\n "items": {\n "oneOf": [\n {\n "properties": {\n "claim_id": {\n "description": "Value for claim id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "comparison_evidence_id": {\n "description": "Exact Evidence identifier for the comparison observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "dimension": {\n "description": "Value for dimension.",\n "enum": [\n "overall",\n "terminal",\n "interaction",\n "exit",\n "filesystem",\n "process"\n ],\n "type": "string"\n },\n "kind": {\n "const": "behavioral",\n "description": "Value for kind.",\n "type": "string"\n },\n "title": {\n "description": "Value for title.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "claim_id",\n "title",\n "comparison_evidence_id",\n "kind",\n "dimension"\n ],\n "type": "object"\n },\n {\n "properties": {\n "claim_id": {\n "description": "Value for claim id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "comparison_evidence_id": {\n "description": "Exact Evidence identifier for the comparison observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "dimension": {\n "description": "Value for dimension.",\n "enum": [\n "overall",\n "identity",\n "pseudocode",\n "assembly",\n "comments",\n "calls",\n "references",\n "strings_names",\n "cfg"\n ],\n "type": "string"\n },\n "kind": {\n "const": "structural-function",\n "description": "Value for kind.",\n "type": "string"\n },\n "title": {\n "description": "Value for title.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "claim_id",\n "title",\n "comparison_evidence_id",\n "kind",\n "dimension"\n ],\n "type": "object"\n },\n {\n "properties": {\n "claim_id": {\n "description": "Value for claim id.",\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "comparison_evidence_id": {\n "description": "Exact Evidence identifier for the comparison observation.",\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "dimension": {\n "const": "overall",\n "description": "Value for dimension.",\n "type": "string"\n },\n "kind": {\n "const": "structural-artifact",\n "description": "Value for kind.",\n "type": "string"\n },\n "title": {\n "description": "Value for title.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "claim_id",\n "title",\n "comparison_evidence_id",\n "kind",\n "dimension"\n ],\n "type": "object"\n }\n ]\n },\n "minItems": 1,\n "type": "array"\n },\n "name": {\n "description": "Exact name used by this operation.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "claims"\n ],\n "type": "object"\n }\n },\n "required": [\n "specification"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "verify_reconstruction",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "evidence": {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "result": {\n "additionalProperties": false,\n "properties": {\n "claims": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "claim_id": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "comparison_evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "dimension": {\n "minLength": 1,\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 3,\n "type": "array"\n },\n "kind": {\n "enum": [\n "behavioral",\n "structural-function",\n "structural-artifact"\n ],\n "type": "string"\n },\n "left_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "observed_status": {\n "enum": [\n "unchanged",\n "added",\n "removed",\n "changed",\n "truncated",\n "unknown",\n "contradiction"\n ],\n "type": "string"\n },\n "right_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "status": {\n "enum": [\n "pass",\n "fail",\n "unknown"\n ],\n "type": "string"\n },\n "unknown_ids": {\n "items": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "claim_id",\n "kind",\n "dimension",\n "status",\n "observed_status",\n "comparison_evidence_id",\n "left_evidence_ids",\n "right_evidence_ids",\n "evidence_links",\n "unknown_ids",\n "limitations"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "items"\n ],\n "type": "object"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 3,\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "recommended_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "claim_ids": {\n "items": {\n "pattern": "^[A-Za-z][A-Za-z0-9._-]*$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n },\n "unknown_ids": {\n "items": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "operation",\n "rationale",\n "claim_ids",\n "unknown_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "specification_sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "status": {\n "enum": [\n "pass",\n "fail",\n "unknown"\n ],\n "type": "string"\n },\n "summary": {\n "additionalProperties": false,\n "properties": {\n "behavioral": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "failed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "passed": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "structural": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "unknown": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "total",\n "passed",\n "failed",\n "unknown",\n "behavioral",\n "structural"\n ],\n "type": "object"\n }\n },\n "required": [\n "status",\n "specification_sha256",\n "summary",\n "claims",\n "recommended_probes",\n "evidence_links",\n "limitations"\n ],\n "type": "object"\n }\n },\n "required": [\n "result",\n "evidence_id",\n "evidence"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Verify Reconstruction"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": true\n },\n "description": "List every current residual-unknown head in deterministic ID order, with optional exact status, severity, and domain filters. Results are complete and inline. This is read-only; unresolved, contradicted, and non-truth dispositions remain distinct.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": false,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {\n "domain": {\n "description": "Value for domain.",\n "minLength": 1,\n "type": "string"\n },\n "severity": {\n "description": "Value for severity.",\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "session",\n "name": "list_unknowns",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "items": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "contradicting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "minLength": 1,\n "type": "string"\n },\n "mutation_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "previous_revision_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "question": {\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "type": {\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "required_confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "const": "verified",\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ]\n },\n "revision": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "revision_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "severity": {\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "revision",\n "previous_revision_digest",\n "revision_digest",\n "scope_digest",\n "question",\n "status",\n "severity",\n "domain",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution",\n "mutation_evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "total": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "items",\n "total"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "List Unknowns"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Create one deterministic residual unknown and immutable mutation evidence. Validates all evidence and relationship references, and rejects duplicate stable identity.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "contradicting_evidence_ids": [],\n "domain": "protocol",\n "question": "Does this branch require an unavailable external service?",\n "recommended_probes": [\n {\n "operation": "capture_process_scenario",\n "rationale": "Replay branch."\n }\n ],\n "relationships": [],\n "required_authority": "controlled-replay",\n "required_confidence": "observed",\n "required_environment": null,\n "severity": "medium",\n "supporting_evidence_ids": []\n }\n ],\n "properties": {\n "contradicting_evidence_ids": {\n "default": [],\n "description": "Ordered Evidence identifiers for the contradicting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "description": "Value for domain.",\n "minLength": 1,\n "type": "string"\n },\n "question": {\n "description": "Concrete unresolved question to retain for later investigation.",\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "description": "Value for recommended probes.",\n "items": {\n "properties": {\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "description": "Value for relationships.",\n "items": {\n "properties": {\n "type": {\n "description": "Value for type.",\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required authority."\n },\n "required_confidence": {\n "description": "Value for required confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for id."\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for isolation."\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for platform."\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required environment."\n },\n "severity": {\n "description": "Value for severity.",\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "default": [],\n "description": "Ordered Evidence identifiers for the supporting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n }\n },\n "required": [\n "question",\n "severity",\n "domain",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "record_unknown",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "contradicting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "minLength": 1,\n "type": "string"\n },\n "mutation_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "previous_revision_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "question": {\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "type": {\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "required_confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "const": "verified",\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ]\n },\n "revision": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "revision_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "severity": {\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "revision",\n "previous_revision_digest",\n "revision_digest",\n "scope_digest",\n "question",\n "status",\n "severity",\n "domain",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution",\n "mutation_evidence_ids"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Record Unknown"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": false,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Append one immutable full-state revision and mutation evidence. Requires exact expected_revision; stale concurrent writers fail instead of overwriting newer analysis.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": false,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "examples": [\n {\n "contradicting_evidence_ids": [],\n "expected_revision": 1,\n "recommended_probes": [],\n "relationships": [],\n "required_authority": "controlled-replay",\n "required_confidence": "observed",\n "required_environment": null,\n "resolution": null,\n "severity": "medium",\n "status": "investigating",\n "supporting_evidence_ids": [],\n "unknown_id": "unk_0000000000000000000000000000000000000000000000000000000000000000"\n }\n ],\n "properties": {\n "contradicting_evidence_ids": {\n "description": "Ordered Evidence identifiers for the contradicting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "expected_revision": {\n "description": "Expected revision used to reject stale or mismatched input.",\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "recommended_probes": {\n "description": "Value for recommended probes.",\n "items": {\n "properties": {\n "operation": {\n "description": "Value for operation.",\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "description": "Value for relationships.",\n "items": {\n "properties": {\n "type": {\n "description": "Value for type.",\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required authority."\n },\n "required_confidence": {\n "description": "Value for required confidence.",\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for architecture."\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for id."\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for isolation."\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for platform."\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for required environment."\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "properties": {\n "disposition": {\n "const": "verified",\n "description": "Value for disposition.",\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "properties": {\n "disposition": {\n "description": "Value for disposition.",\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "description": "Value for evidence ids.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "description": "Value for rationale.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ],\n "description": "Value for resolution."\n },\n "severity": {\n "description": "Value for severity.",\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "description": "Current lifecycle or verification status.",\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "description": "Ordered Evidence identifiers for the supporting observations.",\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "expected_revision",\n "status",\n "severity",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "update_unknown",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "contradicting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "minLength": 1,\n "type": "string"\n },\n "mutation_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "previous_revision_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "question": {\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "type": {\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "required_confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "const": "verified",\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ]\n },\n "revision": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "revision_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "severity": {\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "revision",\n "previous_revision_digest",\n "revision_digest",\n "scope_digest",\n "question",\n "status",\n "severity",\n "domain",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution",\n "mutation_evidence_ids"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Update Unknown"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": true\n },\n "description": "Revalidate the current residual-unknown head against live bundled evidence, exact authority/confidence/environment requirements, and revision integrity. Withdrawn and out-of-scope dispositions are not truth claims.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": false,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "unknown_id": "unk_0000000000000000000000000000000000000000000000000000000000000000"\n }\n ],\n "properties": {\n "unknown_id": {\n "description": "Exact residual-unknown identifier.",\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "verify_unknown_resolution",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "truthVerified": {\n "type": "boolean"\n },\n "unknown": {\n "additionalProperties": false,\n "properties": {\n "contradicting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "minLength": 1,\n "type": "string"\n },\n "mutation_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "previous_revision_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "question": {\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "type": {\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "required_confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "const": "verified",\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ]\n },\n "revision": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "revision_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "severity": {\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "revision",\n "previous_revision_digest",\n "revision_digest",\n "scope_digest",\n "question",\n "status",\n "severity",\n "domain",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution",\n "mutation_evidence_ids"\n ],\n "type": "object"\n },\n "valid": {\n "type": "boolean"\n }\n },\n "required": [\n "valid",\n "truthVerified",\n "unknown"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Verify Unknown Resolution"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": true\n },\n "description": "Return every Evidence record and residual unknown currently retained by this session as one inline bundle for direct inspection or follow-up workflows.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": false,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {},\n "type": "object"\n },\n "kind": "session",\n "name": "get_evidence_bundle",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "artifacts": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n }\n },\n "required": [\n "digest",\n "format",\n "architecture"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "captures": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "predicate_type"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "environments": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "providers": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "records": {\n "items": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "analysis_profile": {\n "additionalProperties": false,\n "properties": {\n "digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n }\n },\n "required": [\n "provider",\n "parameters",\n "digest"\n ],\n "type": "object"\n },\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links",\n "analysis_profile"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n "confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "minLength": 1,\n "type": "string"\n },\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "isolation": {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n "platform": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "evidence_links": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "limitations": {\n "items": {\n "type": "string"\n },\n "type": "array"\n },\n "locations": {\n "items": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "address": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address",\n "type": "string"\n }\n },\n "required": [\n "kind",\n "address"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "minLength": 1,\n "type": "string"\n },\n "kind": {\n "const": "address-range",\n "type": "string"\n },\n "start": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "artifact-path",\n "type": "string"\n },\n "path": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "kind",\n "path"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "kind": {\n "const": "file-offset",\n "type": "string"\n },\n "offset": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "offset"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "end": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n },\n "kind": {\n "const": "file-offset-range",\n "type": "string"\n },\n "start": {\n "maximum": 9007199254740991,\n "minimum": 0,\n "type": "integer"\n }\n },\n "required": [\n "kind",\n "start",\n "end"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "normalized_result": {\n "$ref": "#/$defs/__schema0"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "parameters": {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n },\n "predicate_type": {\n "minLength": 1,\n "type": "string"\n },\n "provider": {\n "additionalProperties": false,\n "properties": {\n "id": {\n "minLength": 1,\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n },\n "version": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "name",\n "version"\n ],\n "type": "object"\n },\n "raw_result": {\n "anyOf": [\n {\n "$ref": "#/$defs/__schema0"\n },\n {\n "type": "null"\n }\n ]\n },\n "subject": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "enum": [\n "x86",\n "x86_64",\n "arm",\n "arm64"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "digest": {\n "additionalProperties": false,\n "properties": {\n "sha256": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "sha256"\n ],\n "type": "object"\n },\n "format": {\n "enum": [\n "hopper",\n "analysis-database",\n "mach-o",\n "elf",\n "pe",\n "dos-mz",\n "dos-com",\n "zip",\n "ipa",\n "apk",\n "msix",\n "appx",\n "asar",\n "dmg",\n "pkg",\n "plist",\n "javascript",\n "source-map",\n "directory",\n "file",\n "unknown",\n "mach-o-universal",\n "javascript-bundle",\n "entitlements"\n ],\n "type": "string"\n },\n "local_path": {\n "type": "string"\n },\n "name": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "name",\n "digest",\n "format",\n "architecture",\n "local_path"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "evidence_id",\n "subject",\n "provider",\n "predicate_type",\n "operation",\n "parameters",\n "raw_result",\n "normalized_result",\n "confidence",\n "authority",\n "environment",\n "limitations",\n "locations",\n "evidence_links"\n ],\n "type": "object"\n }\n ]\n },\n "type": "array"\n },\n "scenarios": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "authority": {\n "const": "controlled-replay",\n "type": "string"\n },\n "evidence_id": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "operation": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "evidence_id",\n "operation",\n "authority"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "unknowns": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "contradicting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "domain": {\n "minLength": 1,\n "type": "string"\n },\n "mutation_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "previous_revision_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "question": {\n "minLength": 1,\n "type": "string"\n },\n "recommended_probes": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "operation": {\n "minLength": 1,\n "type": "string"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "operation",\n "rationale"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "relationships": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "type": {\n "enum": [\n "depends-on",\n "related-to",\n "contradicts"\n ],\n "type": "string"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "type",\n "unknown_id"\n ],\n "type": "object"\n },\n "type": "array"\n },\n "required_authority": {\n "anyOf": [\n {\n "enum": [\n "shipped-artifact",\n "controlled-replay",\n "historical-reference",\n "external-service",\n "analyst-inference"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "required_confidence": {\n "enum": [\n "observed",\n "derived",\n "inferred"\n ],\n "type": "string"\n },\n "required_environment": {\n "anyOf": [\n {\n "additionalProperties": false,\n "properties": {\n "architecture": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "id": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "isolation": {\n "anyOf": [\n {\n "enum": [\n "none",\n "process",\n "container",\n "virtual-machine"\n ],\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "platform": {\n "anyOf": [\n {\n "minLength": 1,\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "id",\n "platform",\n "architecture",\n "isolation"\n ],\n "type": "object"\n },\n {\n "type": "null"\n }\n ]\n },\n "resolution": {\n "anyOf": [\n {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "const": "verified",\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "minItems": 1,\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "disposition": {\n "enum": [\n "withdrawn",\n "out-of-scope"\n ],\n "type": "string"\n },\n "evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "rationale": {\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "disposition",\n "rationale",\n "evidence_ids"\n ],\n "type": "object"\n }\n ]\n },\n {\n "type": "null"\n }\n ]\n },\n "revision": {\n "maximum": 9007199254740991,\n "minimum": 1,\n "type": "integer"\n },\n "revision_digest": {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n "scope_digest": {\n "anyOf": [\n {\n "pattern": "^[a-f0-9]{64}$",\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "severity": {\n "enum": [\n "low",\n "medium",\n "high",\n "critical"\n ],\n "type": "string"\n },\n "status": {\n "enum": [\n "open",\n "investigating",\n "blocked",\n "contradicted",\n "resolved"\n ],\n "type": "string"\n },\n "supporting_evidence_ids": {\n "items": {\n "pattern": "^ev_[a-f0-9]{64}$",\n "type": "string"\n },\n "type": "array"\n },\n "unknown_id": {\n "pattern": "^unk_[a-f0-9]{64}$",\n "type": "string"\n }\n },\n "required": [\n "unknown_id",\n "revision",\n "previous_revision_digest",\n "revision_digest",\n "scope_digest",\n "question",\n "status",\n "severity",\n "domain",\n "supporting_evidence_ids",\n "contradicting_evidence_ids",\n "required_authority",\n "required_confidence",\n "required_environment",\n "recommended_probes",\n "relationships",\n "resolution",\n "mutation_evidence_ids"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "artifacts",\n "providers",\n "environments",\n "scenarios",\n "captures",\n "unknowns",\n "records"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Get Evidence Bundle"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Return the selected document, current address, and containing/current procedure in one provider-neutral result. The result reflects sequential provider observations, not an atomic cursor snapshot; a cursor outside any procedure returns procedure: null.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {}\n ],\n "properties": {\n "document": {\n "description": "Exact provider document or program identity.",\n "minLength": 1,\n "type": "string"\n }\n },\n "type": "object"\n },\n "kind": "session",\n "name": "get_navigation_context",\n "outputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "document": {\n "type": "string"\n },\n "procedure": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n }\n },\n "required": [\n "document",\n "address",\n "procedure"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Get Navigation Context"\n },\n {\n "analysisOperation": null,\n "annotations": {\n "destructiveHint": false,\n "idempotentHint": true,\n "openWorldHint": false,\n "readOnlyHint": false\n },\n "description": "Inspect one explicit reproducible address for its analyzed name, containing procedure, regular and inline comments, and matching bookmarks. Each unsupported facet returns a typed unavailable outcome; use xrefs, assembly, or pseudocode for those deeper views.",\n "effects": {\n "accessesNetwork": false,\n "changesUiState": false,\n "idempotent": true,\n "launchesProcess": false,\n "mayDiscardData": false,\n "mutatesSession": true,\n "mutatesTarget": false,\n "writesFilesystem": false\n },\n "inputSchema": {\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "examples": [\n {\n "address": "0x1000"\n }\n ],\n "properties": {\n "address": {\n "description": "Value for address.",\n "minLength": 1,\n "type": "string"\n },\n "document": {\n "description": "Exact provider document or program identity.",\n "minLength": 1,\n "type": "string"\n }\n },\n "required": [\n "address"\n ],\n "type": "object"\n },\n "kind": "session",\n "name": "inspect_address_context",\n "outputSchema": {\n "$defs": {\n "__schema0": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "number"\n },\n {\n "type": "boolean"\n },\n {\n "type": "null"\n },\n {\n "items": {\n "$ref": "#/$defs/__schema0"\n },\n "type": "array"\n },\n {\n "additionalProperties": {\n "$ref": "#/$defs/__schema0"\n },\n "propertyNames": {\n "type": "string"\n },\n "type": "object"\n }\n ]\n }\n },\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "additionalProperties": false,\n "properties": {\n "result": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "bookmarks": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "available",\n "type": "string"\n },\n "value": {\n "items": {\n "additionalProperties": false,\n "properties": {\n "address": {\n "type": "string"\n },\n "name": {\n "type": "string"\n }\n },\n "required": [\n "address",\n "name"\n ],\n "type": "object"\n },\n "type": "array"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "type": "string"\n },\n "remediation": {\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "comment": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "available",\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "type": "string"\n },\n "remediation": {\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "document": {\n "anyOf": [\n {\n "type": "string"\n },\n {\n "type": "null"\n }\n ]\n },\n "inline_comment": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "available",\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "type": "string"\n },\n "remediation": {\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "name": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "available",\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "type": "string"\n },\n "remediation": {\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "remediation"\n ],\n "type": "object"\n }\n ]\n },\n "procedure": {\n "oneOf": [\n {\n "additionalProperties": false,\n "properties": {\n "state": {\n "const": "available",\n "type": "string"\n },\n "value": {\n "$ref": "#/$defs/__schema0"\n }\n },\n "required": [\n "state",\n "value"\n ],\n "type": "object"\n },\n {\n "additionalProperties": false,\n "properties": {\n "reason": {\n "type": "string"\n },\n "remediation": {\n "type": "string"\n },\n "state": {\n "const": "unavailable",\n "type": "string"\n }\n },\n "required": [\n "state",\n "reason",\n "remediation"\n ],\n "type": "object"\n }\n ]\n }\n },\n "required": [\n "address",\n "document",\n "name",\n "procedure",\n "comment",\n "inline_comment",\n "bookmarks"\n ],\n "type": "object"\n }\n },\n "required": [\n "result"\n ],\n "type": "object"\n },\n "requiresSession": true,\n "title": "Inspect Address Context"\n }\n ]\n}'; -const generatedPayload: unknown = JSON.parse(GENERATED_PAYLOAD_JSON); -if (!isGeneratedPayload(generatedPayload)) - throw new TypeError("Generated MCP catalog payload is invalid"); - -/** Generated from TOOL_CONTRACTS; do not edit. */ -export const GENERATED_MCP_TOOL_CATALOG = generatedPayload.catalog; - -/** Generated lightweight metadata for lazily loaded auxiliary providers. */ -export const GENERATED_AUXILIARY_PROVIDERS = - generatedPayload.auxiliaryProviders; - -function isGeneratedPayload(value: unknown): value is GeneratedPayload { - return ( - isRecord(value) && - Array.isArray(value.catalog) && - value.catalog.every(isGeneratedTool) && - Array.isArray(value.auxiliaryProviders) && - value.auxiliaryProviders.every(isGeneratedProvider) - ); -} - -function isGeneratedTool(value: unknown): value is GeneratedTool { - return ( - isRecord(value) && - typeof value.name === "string" && - (typeof value.analysisOperation === "string" || - value.analysisOperation === null) && - typeof value.title === "string" && - typeof value.description === "string" && - typeof value.kind === "string" && - typeof value.requiresSession === "boolean" && - isRecord(value.inputSchema) && - isRecord(value.outputSchema) && - isRecord(value.annotations) && - isRecord(value.effects) - ); -} - -function isGeneratedProvider(value: unknown): value is GeneratedProvider { - return ( - isRecord(value) && - isRecord(value.identity) && - Array.isArray(value.capabilities) - ); -} - -function isRecord(value: unknown): value is Readonly> { - return typeof value === "object" && value !== null && !Array.isArray(value); -} diff --git a/src/generatedPackageMetadata.ts b/src/generatedPackageMetadata.ts index 1679894f2..531f3ddb8 100644 --- a/src/generatedPackageMetadata.ts +++ b/src/generatedPackageMetadata.ts @@ -1,9 +1,9 @@ /** Generated from package.json and package-lock.json; do not edit. */ export const PACKAGE_METADATA = { name: "rea-agents", - version: "4.1.0", // x-release-please-version + version: "5.0.0", // x-release-please-version serverSdkVersion: "2.3.1", clientSdkVersion: "2.3.1", coreSdkVersion: "2.3.1", - skillVersion: "28", + skillVersion: "29", } as const; diff --git a/src/ghidra/GhidraFunctionValues.test.ts b/src/ghidra/GhidraFunctionValues.test.ts index 7914111d7..f50a3759a 100644 --- a/src/ghidra/GhidraFunctionValues.test.ts +++ b/src/ghidra/GhidraFunctionValues.test.ts @@ -299,23 +299,6 @@ describe("Ghidra jump-table mapping contract", () => { ).toMatchObject({ ok: false, error: { _tag: "AnalysisOutputError" } }); } }); - - it("keeps jump-table sources separate from case-to-target mappings", () => { - const parsed = parseGhidraFunctionResult( - "analyze_function", - ghidraFunctionDossier(), - ); - if (!parsed.ok) throw parsed.error; - const boundary = functionDossierSchema.parse(parsed.value).native_api; - if (boundary?.available !== true) - throw new TypeError("Ghidra native API fixture is unavailable"); - const mapping = boundary.jump_tables[0]?.mappings[0]; - expect(mapping).toMatchObject({ - case_value: 0, - target_address: "0x401020", - }); - expect(mapping).not.toHaveProperty("data_addresses"); - }); }); describe("Ghidra function-analysis malformed results", () => { diff --git a/src/ghidra/GhidraLauncher.ts b/src/ghidra/GhidraLauncher.ts index 82af60f41..444c4b523 100644 --- a/src/ghidra/GhidraLauncher.ts +++ b/src/ghidra/GhidraLauncher.ts @@ -147,6 +147,7 @@ export class GhidraHeadlessLauncher implements GhidraLauncher { platform, command.command, ), + ...(options.signal === undefined ? {} : { signal: options.signal }), }); await writeGhidraRuntimeFile( paths.ownershipPath, diff --git a/src/ghidra/GhidraProvider.behavior.test.ts b/src/ghidra/GhidraProvider.behavior.test.ts index 7bfddef40..9cc337c9e 100644 --- a/src/ghidra/GhidraProvider.behavior.test.ts +++ b/src/ghidra/GhidraProvider.behavior.test.ts @@ -1,15 +1,19 @@ import { createHash } from "node:crypto"; +import { execFile } from "node:child_process"; // Fake-backed provider coverage; real Ghidra verification lives in // `npm run verify:ghidra` and its focused variants. -import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; import { describe, expect, it } from "vitest"; import { fixtureDosLoadImage } from "./GhidraLoadImage.fixture.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { parseConfig } from "../config.js"; import type { BinaryTarget } from "../domain/binaryTarget.js"; +import { parseExecutableHeader } from "../domain/binaryTarget.js"; import { createAnalysisProfile } from "../domain/analysisProfile.js"; import { GHIDRA_PROVIDER_IDENTITY, @@ -257,14 +261,18 @@ describe("Ghidra platform support", () => { const ghidra = provider({ ...installationHost(), platform: "win32" }); const nativeApplication = peTarget("x86_64"); - expect(ghidra.inspectTargetSupport(nativeApplication)).toMatchObject({ - status: "supported", - diagnostics: { - host_platform: "win32", - executable_role: "application", - managed: false, - }, - }); + for (const architecture of ["x86", "x86_64"] as const) + expect(ghidra.inspectTargetSupport(peTarget(architecture))).toMatchObject( + { + status: "supported", + diagnostics: { + host_platform: "win32", + architecture, + executable_role: "application", + managed: false, + }, + }, + ); expect(ghidra.inspectAvailability()).toMatchObject({ status: "unavailable", code: "unsupported_host", @@ -333,6 +341,46 @@ describe("Ghidra platform support", () => { code: "architecture_unsupported", }); }); + it("admits generated PE32 applications while rejecting unsupported x86 roles", async () => { + const root = fileURLToPath(new URL("../../", import.meta.url)); + await promisify(execFile)(process.execPath, [ + join(root, "scripts/create-ghidra-windows-fixture.mjs"), + ]); + const path = join(root, "build/fixtures/rea-ghidra-windows-x86.exe"); + const bytes = await readFile(path); + const metadata = parseExecutableHeader(bytes, "x64"); + expect(metadata).toMatchObject({ + ok: true, + value: { + format: "pe", + architecture: "x86", + executableRole: "application", + managed: false, + }, + }); + if (!metadata.ok) throw new Error(metadata.error); + if (metadata.value.format !== "pe") + throw new Error("Expected a PE fixture"); + const target: BinaryTarget = { + path, + sha256: createHash("sha256").update(bytes).digest("hex"), + kind: "executable", + ...metadata.value, + }; + const ghidra = provider({ ...installationHost(), platform: "win32" }); + expect(ghidra.inspectTargetSupport(target).status).toBe("supported"); + for (const [candidate, code] of [ + [ + { ...target, executableRole: "shared-library" }, + "target_role_unsupported", + ], + [{ ...target, managed: true }, "managed_target_unsupported"], + ] as const) + expect(ghidra.inspectTargetSupport(candidate)).toMatchObject({ + status: "unsupported", + code, + }); + }); }); describe("Ghidra client projection", () => { diff --git a/src/ghidra/GhidraProvider.test.ts b/src/ghidra/GhidraProvider.test.ts deleted file mode 100644 index c38e7ed75..000000000 --- a/src/ghidra/GhidraProvider.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { parseConfig } from "../config.js"; -import { silentLogger } from "../logger.js"; -import { GHIDRA_FUNCTION_OPERATIONS } from "./GhidraFunctionValues.js"; -import { GHIDRA_INVENTORY_OPERATIONS } from "./GhidraInventoryValues.js"; -import { GhidraProvider } from "./GhidraProvider.js"; - -describe("Ghidra provider capabilities", () => { - it("publishes admitted operation effects and resists caller mutation", () => { - const config = parseConfig({}); - expect(config.ok).toBe(true); - if (!config.ok) return; - const provider = new GhidraProvider(config.value, silentLogger, { - platform: "linux", - architecture: "x64", - readText: () => undefined, - executable: () => false, - probeJava: () => undefined, - }); - const capabilities = provider.capabilities(); - const published = structuredClone(capabilities); - expect(capabilities.map(({ operation }) => operation).sort()).toEqual( - [...GHIDRA_INVENTORY_OPERATIONS, ...GHIDRA_FUNCTION_OPERATIONS].sort(), - ); - for (const descriptor of capabilities) { - expect(descriptor).toMatchObject({ - available: true, - reason: null, - effects: { - mutatesArtifact: descriptor.operation === "annotate_native_function", - mayShowUi: false, - mayAccessNetwork: false, - changesPermissions: false, - requiresRoot: false, - }, - }); - } - const baseLimitations = capabilities.find( - (item) => item.operation === "address_name", - )?.limitations; - for (const operation of ["search_procedures", "search_strings"]) { - expect( - capabilities.find((item) => item.operation === operation)?.limitations, - ).toEqual(baseLimitations); - } - const first = capabilities[0]; - if (first === undefined) throw new Error("Ghidra capabilities are empty"); - Reflect.set(capabilities, 0, { ...first, available: false }); - Reflect.set(first, "available", false); - Reflect.set(first.effects, "mutatesArtifact", true); - Reflect.set(first.limitations, 0, "forged limitation"); - - expect(provider.capabilities()).toEqual(published); - }); -}); diff --git a/src/ghidra/GhidraProvider.ts b/src/ghidra/GhidraProvider.ts index 17ef5cf20..008a3808c 100644 --- a/src/ghidra/GhidraProvider.ts +++ b/src/ghidra/GhidraProvider.ts @@ -233,11 +233,11 @@ const inspectWindowsP0TargetSupport = ( reason: "Windows Ghidra P0 accepts PE targets only.", diagnostics, }; - if (target.architecture !== "x86_64") + if (target.architecture !== "x86" && target.architecture !== "x86_64") return { status: "unsupported", code: "architecture_unsupported", - reason: "Windows Ghidra P0 accepts x86-64 PE targets only.", + reason: "Windows Ghidra P0 accepts x86 and x86-64 PE targets only.", diagnostics, }; if (target.executableRole !== "application") diff --git a/src/ghidra/GhidraProviderCapabilities.ts b/src/ghidra/GhidraProviderCapabilities.ts index 0bec2afa0..402f6d1e8 100644 --- a/src/ghidra/GhidraProviderCapabilities.ts +++ b/src/ghidra/GhidraProviderCapabilities.ts @@ -30,7 +30,7 @@ export const healthLimitations = Object.freeze([ /** Additional limitations applied to the experimental Windows x64 P0 boundary. */ export const windowsP0Limitations = Object.freeze([ - "Windows Ghidra P0 accepts approved native x86-64 PE applications only; DLL, managed, hostile, sensitive, and mutable-path targets are unsupported.", + "Windows Ghidra P0 accepts approved native x86 and x86-64 PE applications only; DLL, managed, hostile, sensitive, and mutable-path targets are unsupported.", "The Windows bridge uses authenticated IPv4 loopback because Node path-based IPC does not expose Java AF_UNIX sockets; the endpoint file contains no bearer token.", "Windows sessions require the matching packaged Windows x64 native addon, local NTFS targets and runtimes, and Windows 10 or later. Native handles enforce path admission, private DACLs, and Job Object ownership automatically.", ]); diff --git a/src/ghidra/GhidraResponseBuffer.test.ts b/src/ghidra/GhidraResponseBuffer.test.ts deleted file mode 100644 index ccae6d051..000000000 --- a/src/ghidra/GhidraResponseBuffer.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { GhidraResponseBuffer } from "./GhidraResponseBuffer.js"; - -describe("Ghidra response buffer", () => { - it("preserves fragmented lines, whitespace and a trailing partial response", () => { - const lines: string[] = []; - const buffer = new GhidraResponseBuffer({ - onLine: (line) => lines.push(line), - }); - buffer.push(' {"result":"雪'); - buffer.push('🦊"}\r'); - buffer.push('\n\n {"result":2}\n{"res'); - expect(lines).toEqual(['{"result":"雪🦊"}', '{"result":2}']); - buffer.push('ult":3}'); - expect(lines).toHaveLength(2); - buffer.push("\n"); - expect(lines).toEqual([ - '{"result":"雪🦊"}', - '{"result":2}', - '{"result":3}', - ]); - }); - - it("drops both pending fragments and remaining lines when reset during delivery", () => { - const lines: string[] = []; - const buffer = new GhidraResponseBuffer({ - onLine: (line) => { - lines.push(line); - buffer.reset(); - }, - }); - buffer.push("discarded partial"); - buffer.reset(); - buffer.push("first\nsecond\npartial"); - buffer.push("discarded"); - buffer.reset(); - buffer.push("next\n"); - expect(lines).toEqual(["first", "next"]); - }); - - it("accepts a large response arriving in many small socket fragments", () => { - const lines: string[] = []; - const buffer = new GhidraResponseBuffer({ - onLine: (line) => lines.push(line), - }); - const response = `{"result":"${"x".repeat(8 * 1024 * 1024)}"}`; - for (let offset = 0; offset < response.length; offset += 4096) - buffer.push(response.slice(offset, offset + 4096)); - expect(lines).toEqual([]); - buffer.push("\n"); - expect(lines).toEqual([response]); - }); - - it("accepts complete responses larger than the former 64 MiB ceiling", () => { - const lines: string[] = []; - const buffer = new GhidraResponseBuffer({ - onLine: (line) => lines.push(line), - }); - const response = `{"result":"${"x".repeat(64 * 1024 * 1024 + 1)}"}`; - - buffer.push(`${response}\n`); - - expect(lines).toEqual([response]); - }); -}); diff --git a/src/hopper/BridgeLauncher.ts b/src/hopper/BridgeLauncher.ts index efa5563f5..af60844e2 100644 --- a/src/hopper/BridgeLauncher.ts +++ b/src/hopper/BridgeLauncher.ts @@ -336,6 +336,7 @@ const launchHopperProcess = async (input: { arguments: arguments_, runId: input.session.runId, expectedCommand: ownershipCommand, + ...(input.signal === undefined ? {} : { signal: input.signal }), }); if (input.linuxDemo !== undefined) return spawnOwnedProviderProcess({ @@ -343,6 +344,7 @@ const launchHopperProcess = async (input: { arguments: input.linuxDemo.args, runId: input.session.runId, expectedCommand: ownershipCommand, + ...(input.signal === undefined ? {} : { signal: input.signal }), }); return spawn(input.argumentsForTarget); }; diff --git a/src/hopper/HopperProvider.test.ts b/src/hopper/HopperProvider.test.ts index 2fdb8e06e..8b08582e9 100644 --- a/src/hopper/HopperProvider.test.ts +++ b/src/hopper/HopperProvider.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { parseConfig } from "../config.js"; import { silentLogger } from "../logger.js"; -import { HopperProvider, HOPPER_OPERATIONS } from "./HopperProvider.js"; +import { HopperProvider } from "./HopperProvider.js"; describe("Hopper provider capabilities", () => { it("declines DOS MZ targets with a provider-specific support reason", () => { @@ -24,42 +24,4 @@ describe("Hopper provider capabilities", () => { reason: expect.stringContaining("Ghidra"), }); }); - - it("publishes deterministic descriptors and resists caller mutation", () => { - const config = parseConfig({}); - expect(config.ok).toBe(true); - if (!config.ok) throw new Error("expected valid configuration"); - const provider = new HopperProvider(config.value, silentLogger); - const capabilities = provider.capabilities(); - const published = structuredClone(capabilities); - expect(capabilities.map(({ operation }) => operation)).toEqual([ - ...HOPPER_OPERATIONS, - ]); - expect(new Set(capabilities.map(({ operation }) => operation)).size).toBe( - capabilities.length, - ); - for (const descriptor of capabilities) { - expect(descriptor.provider).toEqual(provider.identity()); - expect(descriptor).toMatchObject({ - available: true, - reason: null, - }); - } - expect( - capabilities.find(({ operation }) => operation === "list_procedures"), - ).toMatchObject({ available: true }); - expect( - capabilities.find(({ operation }) => operation === "set_comment"), - ).toMatchObject({ - effects: { mutatesArtifact: true, mayWriteFilesystem: true }, - }); - const first = capabilities[0]; - if (first === undefined) throw new Error("Hopper capabilities are empty"); - Reflect.set(capabilities, 0, { ...first, available: false }); - Reflect.set(first, "available", false); - Reflect.set(first.effects, "mutatesArtifact", true); - Reflect.set(first.limitations, 0, "forged limitation"); - - expect(provider.capabilities()).toEqual(published); - }); }); diff --git a/src/hopper/HopperResponseStream.test.ts b/src/hopper/HopperResponseStream.test.ts deleted file mode 100644 index fe3bd7ab6..000000000 --- a/src/hopper/HopperResponseStream.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { HopperResponseStream } from "./HopperResponseStream.js"; - -describe("Hopper response stream", () => { - it("preserves fragmented messages and drops partial bytes on reset", () => { - const messages: unknown[] = []; - const stream = new HopperResponseStream({ - accept: (message) => { - messages.push(message); - return true; - }, - hasQueued: () => false, - nextRequestId: () => 4, - abort: (message) => { - throw new Error(message); - }, - }); - stream.push("discarded"); - stream.reset(); - stream.push(' {"id":1,"result":"雪'); - stream.push('🦊"}\r'); - stream.push('\n\n{"id":2,"result":null}\n{"id":'); - stream.push('3,"result":true}'); - expect(messages).toEqual([ - { id: 1, result: "雪🦊" }, - { id: 2, result: null }, - ]); - stream.push("\n"); - expect(messages).toEqual([ - { id: 1, result: "雪🦊" }, - { id: 2, result: null }, - { id: 3, result: true }, - ]); - }); - - it("stops delivering queued lines when validation aborts and resets the stream", () => { - const messages: unknown[] = []; - const failures: string[] = []; - const stream = new HopperResponseStream({ - accept: (message) => { - messages.push(message); - return true; - }, - hasQueued: () => false, - nextRequestId: () => 2, - abort: (message) => { - failures.push(message); - stream.reset(); - }, - }); - stream.push('malformed\n{"id":1,"result":true}\n'); - expect(messages).toEqual([]); - expect(failures).toHaveLength(1); - stream.push('{"id":1,"result":false}\n'); - expect(messages).toEqual([{ id: 1, result: false }]); - }); - - it("accepts a large response arriving in many small socket fragments", () => { - const messages: unknown[] = []; - const stream = new HopperResponseStream({ - accept: (message) => { - messages.push(message); - return true; - }, - hasQueued: () => false, - nextRequestId: () => 2, - abort: (message) => { - throw new Error(message); - }, - }); - const response = `{"id":1,"result":"${"x".repeat(8 * 1024 * 1024)}"}`; - for (let offset = 0; offset < response.length; offset += 4096) - stream.push(response.slice(offset, offset + 4096)); - expect(messages).toEqual([]); - stream.push("\n"); - expect(messages).toEqual([JSON.parse(response)]); - }); - - it("accepts complete responses larger than the former 10 MiB ceiling", () => { - const messages: unknown[] = []; - const failures: string[] = []; - const stream = new HopperResponseStream({ - accept: (message) => { - messages.push(message); - return true; - }, - hasQueued: () => false, - nextRequestId: () => 2, - abort: (message) => failures.push(message), - }); - const line = `{"id":1,"result":"${"x".repeat(10 * 1024 * 1024 + 1)}"}`; - - stream.push(`${line}\n`); - - expect(failures).toEqual([]); - expect(messages).toEqual([JSON.parse(line)]); - }); -}); diff --git a/src/hopper/LinuxPrivateDisplayProbe.ts b/src/hopper/LinuxPrivateDisplayProbe.ts index 1ec6190d0..27f136185 100644 --- a/src/hopper/LinuxPrivateDisplayProbe.ts +++ b/src/hopper/LinuxPrivateDisplayProbe.ts @@ -20,6 +20,9 @@ const PYTHON_PATH = "/usr/bin/python3"; const UNSHARE_PATH = "/usr/bin/unshare"; const DEFAULT_PROBE_TIMEOUT_MS = 5_000; +const isAborted = (signal: AbortSignal | undefined): boolean => + signal?.aborted === true; + export type LinuxPrivateDisplayRunnableStrategy = Exclude< HopperPrivateDisplayStrategy, "unavailable" @@ -101,8 +104,7 @@ export const selectLinuxPrivateDisplayStrategy = async (options: { /** Run one helper probe in an owned process group and retain its full diagnostics. */ export const runLinuxPrivateDisplayProbe: LinuxPrivateDisplayProbeRunner = async (strategy, options) => { - if (options.signal?.aborted === true) - return emptyProcessResult("cancelled"); + if (isAborted(options.signal)) return emptyProcessResult("cancelled"); const command = privateDisplayProbeCommand(options.helperPath, strategy); let started: Awaited>; try { @@ -111,11 +113,14 @@ export const runLinuxPrivateDisplayProbe: LinuxPrivateDisplayProbeRunner = arguments: command.arguments, runId: randomUUID(), expectedCommand: null, + ...(options.signal === undefined ? {} : { signal: options.signal }), }); } catch (cause: unknown) { // best-effort cleanup: probe spawn failure is reported as launch-failed. void cause; - return emptyProcessResult("launch-failed"); + return emptyProcessResult( + isAborted(options.signal) ? "cancelled" : "launch-failed", + ); } const launch: ProviderProcessLaunch = { process: started.process, diff --git a/src/ida/IdaProviderCapabilities.test.ts b/src/ida/IdaProviderCapabilities.test.ts deleted file mode 100644 index cc36a62b3..000000000 --- a/src/ida/IdaProviderCapabilities.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect, it } from "vitest"; - -import { IDA_OPERATIONS, idaCapabilities } from "./IdaProviderCapabilities.js"; - -it("preserves live read-only capabilities when callers attempt to alter declarations", () => { - const original = idaCapabilities("headless", true); - Reflect.set(IDA_OPERATIONS, 0, "set_comment"); - Reflect.set(IDA_OPERATIONS, IDA_OPERATIONS.length, "set_address_name"); - - expect(idaCapabilities("headless", true)).toEqual(original); - expect(original.every(({ cachePolicy }) => cachePolicy === "live")).toBe( - true, - ); - expect(original.every(({ effects }) => !effects.mutatesArtifact)).toBe(true); - expect( - original.find(({ operation }) => operation === "procedure_callers"), - ).toMatchObject({ available: false }); -}); diff --git a/src/inspector/JavaScriptRuntimeScope.test.ts b/src/inspector/JavaScriptRuntimeScope.test.ts deleted file mode 100644 index caa876dee..000000000 --- a/src/inspector/JavaScriptRuntimeScope.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - authorizeRuntimeLocation, - inspectorExclusionKey, -} from "./JavaScriptRuntimeScope.js"; - -describe("runtime location authorization", () => { - it("reports canonical exclusion reasons per denial case", async () => { - expect(await authorizeRuntimeLocation("not a url")).toEqual({ - allowed: false, - reason: "unsupported_url", - }); - expect(await authorizeRuntimeLocation("ftp://example.test/app.js")).toEqual( - { - allowed: false, - reason: "unsupported_url", - }, - ); - expect( - await authorizeRuntimeLocation("file:///outside-approved-scope.js"), - ).toEqual({ allowed: false, reason: "not_approved" }); - }); - - it("collapses every canonical denial onto the fixed wire bucket", () => { - expect(inspectorExclusionKey("unsupported_url")).toBe( - "unsupported_location", - ); - expect(inspectorExclusionKey("not_approved")).toBe("unsupported_location"); - expect(inspectorExclusionKey("disallowed_origin")).toBe( - "unsupported_location", - ); - }); -}); diff --git a/src/inspector/V8InspectorCaptureProjection.test.ts b/src/inspector/V8InspectorCaptureProjection.test.ts new file mode 100644 index 000000000..124745e67 --- /dev/null +++ b/src/inspector/V8InspectorCaptureProjection.test.ts @@ -0,0 +1,216 @@ +import { mkdtemp, realpath, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; + +import { afterAll, beforeAll, expect, it } from "vitest"; + +import { finalizeInspectorCapture } from "./V8InspectorCaptureProjection.js"; +import type { CaptureState } from "./V8InspectorProvider.js"; + +let root: string; +beforeAll(async () => { + root = await mkdtemp(join(tmpdir(), "rea-inspector-projection-")); +}); +afterAll(async () => { + if (root !== undefined) await rm(root, { recursive: true, force: true }); +}); + +const target = { + id: "target-1", + type: "node", + url: "file:///tmp/target.js", + attached: false, + webSocketUrl: "ws://127.0.0.1:9222/target-1", + location: { kind: "file" as const, file_path: "/tmp/target.js" }, +}; +const stateFor = (scripts: CaptureState["scripts"]): CaptureState => ({ + scripts, + contexts: new Map(), + eventsObserved: scripts.length, + eventsRetained: scripts.length, + eventsDropped: 0, + metadataBytes: 0, + scriptsObserved: scripts.length, + invalidScripts: 0, + truncated: false, + truncationReasons: new Set(), +}); +const finalize = ( + state: CaptureState, + authorizeLocation?: Parameters< + typeof finalizeInspectorCapture + >[0]["authorizeLocation"], +) => + finalizeInspectorCapture({ + input: { + inspector_endpoint: "http://127.0.0.1:9222", + target_id: target.id, + observation_ms: 100, + }, + runtime: { + product: "Node.js/v24.18.0", + protocol_version: "1.3", + v8_version: null, + }, + target, + state, + ...(authorizeLocation === undefined ? {} : { authorizeLocation }), + }); + +it("authorizes distinct Inspector locations and preserves stable deduplication", async () => { + const firstPath = join(root, "first.js"); + const secondPath = join(root, "second.js"); + await Promise.all([ + writeFile(firstPath, "void 0;\n"), + writeFile(secondPath, "void 1;\n"), + ]); + const firstUrl = pathToFileURL(firstPath).href; + const secondUrl = pathToFileURL(secondPath).href; + const result = await finalize( + stateFor([ + { + rawUrl: firstUrl, + executionContextKey: "1", + cdpHash: "first", + length: 7, + isModule: false, + }, + { + rawUrl: firstUrl, + executionContextKey: "1", + cdpHash: "first", + length: 7, + isModule: false, + }, + { + rawUrl: secondUrl, + executionContextKey: "1", + cdpHash: "second", + length: 7, + isModule: false, + }, + { + rawUrl: "ftp://example.test/unapproved.js", + executionContextKey: null, + cdpHash: null, + length: 0, + isModule: false, + }, + ]), + ); + + expect(result.scripts.items.map(({ location }) => location)).toEqual( + expect.arrayContaining([ + { kind: "file", file_path: await realpath(firstPath) }, + { kind: "file", file_path: await realpath(secondPath) }, + ]), + ); + expect(result.scripts.items).toHaveLength(2); + expect(result.scripts.excluded).toEqual({ + unsupported_location: 1, + invalid_protocol_value: 0, + }); +}); + +it("authorizes each unique script location through a bounded worker pool", async () => { + const drafts = Array.from({ length: 200 }, (_, index) => ({ + rawUrl: `file:///fixture/script-${String(index)}.js`, + executionContextKey: "1", + cdpHash: `hash-${String(index)}`, + length: 7, + isModule: false, + })); + const state = stateFor([ + ...drafts, + ...drafts.slice(0, 1), + { + rawUrl: "ftp://example.test/unapproved.js", + executionContextKey: null, + cdpHash: null, + length: 0, + isModule: false, + }, + ]); + const activeUrls = new Set(); + const authorizedUrls = new Set(); + let maximumActive = 0; + const result = await finalize(state, async (rawUrl) => { + activeUrls.add(rawUrl); + authorizedUrls.add(rawUrl); + maximumActive = Math.max(maximumActive, activeUrls.size); + await new Promise((resolve) => setTimeout(resolve, 1)); + activeUrls.delete(rawUrl); + return rawUrl.startsWith("ftp:") + ? { allowed: false, reason: "unsupported_url" } + : { allowed: true, location: { kind: "file", file_path: rawUrl } }; + }); + + expect(maximumActive).toBeGreaterThan(1); + expect(maximumActive).toBeLessThan(authorizedUrls.size); + expect(authorizedUrls.size).toBe(201); + expect(activeUrls.size).toBe(0); + expect(result.scripts.items).toHaveLength(200); + expect(result.scripts.observed_total).toBe(202); + expect(result.scripts.excluded).toEqual({ + unsupported_location: 1, + invalid_protocol_value: 0, + }); +}); + +it("waits for active authorization workers to settle before returning a failure", async () => { + const scripts = Array.from({ length: 12 }, (_, index) => ({ + rawUrl: `file:///fixture/failure-${String(index)}.js`, + executionContextKey: null, + cdpHash: null, + length: 0, + isModule: false, + })); + const startedUrls: string[] = []; + const pending = new Map< + string, + { readonly resolve: () => void; readonly reject: (error: Error) => void } + >(); + const failure = new Error("authorization failed"); + let active = 0; + let maximumActive = 0; + let failureInjected = false; + + const finalization = finalize(stateFor(scripts), async (rawUrl) => { + startedUrls.push(rawUrl); + if (failureInjected) + return { allowed: true, location: { kind: "file", file_path: rawUrl } }; + active += 1; + maximumActive = Math.max(maximumActive, active); + return new Promise<{ + readonly allowed: true; + readonly location: { readonly kind: "file"; readonly file_path: string }; + }>((resolve, reject) => { + pending.set(rawUrl, { + resolve: () => + resolve({ + allowed: true, + location: { kind: "file", file_path: rawUrl }, + }), + reject, + }); + }).finally(() => { + active -= 1; + }); + }); + const initialStarted = new Set(startedUrls); + expect(initialStarted.size).toBeGreaterThan(1); + expect(maximumActive).toBe(initialStarted.size); + const failingUrl = startedUrls[0]; + if (failingUrl === undefined) + throw new Error("Expected at least one active authorization"); + failureInjected = true; + pending.get(failingUrl)?.reject(failure); + await Promise.resolve(); + expect(new Set(startedUrls)).toEqual(initialStarted); + for (const [rawUrl, request] of pending) + if (rawUrl !== failingUrl) request.resolve(); + await expect(finalization).rejects.toBe(failure); + expect(new Set(startedUrls)).toEqual(initialStarted); + expect(active).toBe(0); +}); diff --git a/src/inspector/V8InspectorCaptureProjection.ts b/src/inspector/V8InspectorCaptureProjection.ts index 84d1e00af..102247afb 100644 --- a/src/inspector/V8InspectorCaptureProjection.ts +++ b/src/inspector/V8InspectorCaptureProjection.ts @@ -38,6 +38,14 @@ interface FinalizeCaptureInput { readonly runtime: JavaScriptRuntimeTargetList["runtime"]; readonly target: AuthorizedV8InspectorTarget; readonly state: CaptureState; + readonly authorizeLocation?: typeof authorizeRuntimeLocation; +} + +const LOCATION_AUTHORIZATION_WORKERS = 8; + +interface AuthorizedLocationGroup { + readonly drafts: readonly ScriptDraft[]; + readonly decision: Awaited>; } /** Canonically authorize, deduplicate, and sort one bounded raw capture. */ @@ -46,20 +54,61 @@ export const finalizeInspectorCapture = async ({ runtime, target, state, + authorizeLocation = authorizeRuntimeLocation, }: FinalizeCaptureInput): Promise => { const exclusions = createInspectorExclusionCounts(); const scripts = new Map< string, JavaScriptRuntimeObservation["scripts"]["items"][number] >(); + const draftsByUrl = new Map(); for (const draft of state.scripts) { - const decision = await authorizeRuntimeLocation(draft.rawUrl); - if (!decision.allowed) { - exclusions[inspectorExclusionKey(decision.reason)] += 1; - continue; + const drafts = draftsByUrl.get(draft.rawUrl) ?? []; + drafts.push(draft); + draftsByUrl.set(draft.rawUrl, drafts); + } + const groups = [...draftsByUrl]; + const authorizedGroups: AuthorizedLocationGroup[] = new Array(groups.length); + let nextGroup = 0; + let stopScheduling = false; + const worker = async (): Promise => { + try { + for (;;) { + if (stopScheduling) return; + const index = nextGroup; + nextGroup += 1; + const group = groups[index]; + if (group === undefined) return; + const [rawUrl, drafts] = group; + authorizedGroups[index] = { + drafts, + decision: await authorizeLocation(rawUrl), + }; + } + } catch (cause: unknown) { + stopScheduling = true; + throw cause; + } + }; + const workerResults = await Promise.allSettled( + Array.from( + { length: Math.min(LOCATION_AUTHORIZATION_WORKERS, groups.length) }, + worker, + ), + ); + const failedWorker = workerResults.find( + (result) => result.status === "rejected", + ); + if (failedWorker?.status === "rejected") throw failedWorker.reason; + for (const { drafts, decision } of authorizedGroups) { + for (const draft of drafts) { + if (!decision.allowed) { + exclusions[inspectorExclusionKey(decision.reason)] += 1; + continue; + } + const script = scriptFromDraft(draft, decision.location); + scripts.set(script.script_key, script); } - const script = scriptFromDraft(draft, decision.location); - scripts.set(script.script_key, script); } const items = [...scripts.values()].sort((left, right) => left.script_key < right.script_key diff --git a/src/javascript/recovery/WakaruCommand.ts b/src/javascript/recovery/WakaruCommand.ts index ad90338ef..42062feee 100644 --- a/src/javascript/recovery/WakaruCommand.ts +++ b/src/javascript/recovery/WakaruCommand.ts @@ -101,8 +101,11 @@ export const runWakaruCommand = async (context: WakaruCommandContext) => { runId, cwd: context.cwd, hostEnvironment: context.environment, + ...(context.signal === undefined ? {} : { signal: context.signal }), env: { TMPDIR: context.cwd, RAYON_NUM_THREADS: "1", NO_COLOR: "1" }, }).catch((cause: unknown) => { + if (context.signal?.aborted === true) + throw new AnalysisCancelledError(OPERATION); throw new ProviderAdapterError("wakaru", OPERATION, { cause, diagnostics: { diff --git a/src/javascript/sourceMaps/SourceMapCommand.ts b/src/javascript/sourceMaps/SourceMapCommand.ts index 62df4142c..eec1f8844 100644 --- a/src/javascript/sourceMaps/SourceMapCommand.ts +++ b/src/javascript/sourceMaps/SourceMapCommand.ts @@ -120,6 +120,7 @@ class OwnedCodec { runId: this.#runId, cwd: this.#runtime.path, hostEnvironment: environment, + signal: this.deadline.signal, }); this.#supervisor = new ProviderProcessSupervisor( { diff --git a/src/native/NativeUiHelperRuntime.ts b/src/native/NativeUiHelperRuntime.ts index e4571ec91..95e488895 100644 --- a/src/native/NativeUiHelperRuntime.ts +++ b/src/native/NativeUiHelperRuntime.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url"; import { execFileOutput } from "../process/ExecFileOutput.js"; import { safeParseJson } from "../domain/safeJson.js"; import type { NativeUiHelper } from "./NativeUiObservation.js"; +import { NATIVE_UI_HELPER_MAX_BUFFER } from "./NativeUiOutputBudget.js"; /** Lazily compile one owned helper per observation/scenario and remove its compiler cache. */ export const createNativeUiHelperRuntime = () => { @@ -50,7 +51,7 @@ export const createNativeUiHelperRuntime = () => { [JSON.stringify(parameters)], { timeout: 30_000, - maxBuffer: 16 * 1024 * 1024, + maxBuffer: NATIVE_UI_HELPER_MAX_BUFFER, ...(signal === undefined ? {} : { signal }), }, ); diff --git a/src/native/NativeUiObservation.test.ts b/src/native/NativeUiObservation.test.ts index 0eda0258f..b341ec617 100644 --- a/src/native/NativeUiObservation.test.ts +++ b/src/native/NativeUiObservation.test.ts @@ -62,7 +62,6 @@ describe("native UI screenshot validation", () => { { base64: "%%%=" }, { base64: Buffer.from("not a PNG").toString("base64") }, { width: 2 }, - { sha256: "0".repeat(64) }, ])( "rejects invalid screenshot field variants from the helper", async (change) => { @@ -111,6 +110,37 @@ describe("native UI screenshot validation", () => { ); expect(result.ok).toBe(true); }); + + it("preserves an unknown AX child count and its truncation gap", async () => { + const partial = { + ...snapshot, + nodes: [ + { + path: [], + role: "AXWindow", + title: "Fixture", + value: null, + actions: [], + children_count: null, + }, + ], + truncated: true, + gaps: ["AX child count unavailable at path []: AXError -25204"], + }; + const result = await observeNativeUi( + target, + "observe_native_ui", + { ...scope, accessibility: true }, + { invoke: async () => ({ ok: true, result: partial }) }, + ); + expect(result.ok).toBe(true); + if (result.ok) + expect(result.value.initial).toMatchObject({ + truncated: true, + gaps: ["AX child count unavailable at path []: AXError -25204"], + nodes: [{ children_count: null }], + }); + }); }); describe("native UI capture selection and budgets", () => { @@ -205,6 +235,27 @@ describe("native UI capture selection and budgets", () => { outcome: "cancelled", }); }); + it("reports aggregate output exhaustion after individually valid captures", async () => { + const largeSnapshot = { + ...snapshot, + window: { ...snapshot.window, title: "x".repeat(17 * 1024 * 1024) }, + }; + const result = await observeNativeUi( + target, + "capture_native_ui_scenario", + { + ...scope, + steps: [{ kind: "wait", milliseconds: 0 }], + }, + { invoke: async () => ({ ok: true, result: largeSnapshot }) }, + ); + expect(result.ok).toBe(true); + if (result.ok) + expect(result.value.steps[0]).toMatchObject({ + outcome: "failed", + reason: expect.stringContaining("64 MiB output budget"), + }); + }); }); describe("native UI target and cancellation failures", () => { diff --git a/src/native/NativeUiObservation.ts b/src/native/NativeUiObservation.ts index a3273543a..5d186875d 100644 --- a/src/native/NativeUiObservation.ts +++ b/src/native/NativeUiObservation.ts @@ -16,6 +16,10 @@ import { ProviderAdapterError } from "../domain/providerAdapterError.js"; import { type AnalysisError } from "../domain/analysisErrorBase.js"; import { err, ok, type Result } from "../domain/result.js"; import { createNativeUiHelperRuntime } from "./NativeUiHelperRuntime.js"; +import { + NATIVE_UI_OUTPUT_BUDGET_BYTES, + NATIVE_UI_OUTPUT_WEIGHT, +} from "./NativeUiOutputBudget.js"; const helper = fileURLToPath( new URL("../../bridge/native/ReaNativeUI.swift", import.meta.url), @@ -135,8 +139,10 @@ const observeWithHelper = async ( ), ); launchTime = response.result.window.launch_time; - outputBytes += 2 * Buffer.byteLength(JSON.stringify(response.result)); - if (outputBytes > 64 * 1024 * 1024) + outputBytes += + NATIVE_UI_OUTPUT_WEIGHT * + Buffer.byteLength(JSON.stringify(response.result)); + if (outputBytes > NATIVE_UI_OUTPUT_BUDGET_BYTES) return err( new AnalysisCapabilityUnavailableError( "native-macos", diff --git a/src/native/NativeUiOutputBudget.ts b/src/native/NativeUiOutputBudget.ts new file mode 100644 index 000000000..538c5cae3 --- /dev/null +++ b/src/native/NativeUiOutputBudget.ts @@ -0,0 +1,15 @@ +const HELPER_SUCCESS_WRAPPER = Buffer.from( + JSON.stringify({ ok: true, result: {} }), +); + +/** Scenario output is charged at twice the encoded result size before return. */ +export const NATIVE_UI_OUTPUT_BUDGET_BYTES = 64 * 1024 * 1024; +export const NATIVE_UI_OUTPUT_WEIGHT = 2; + +/** Bytes added around a result by the helper's success JSON response. */ +export const NATIVE_UI_HELPER_ENVELOPE_BYTES = + HELPER_SUCCESS_WRAPPER.byteLength - Buffer.byteLength("{}"); + +/** Raw helper output is bounded by the scenario's existing unweighted byte budget. */ +export const NATIVE_UI_HELPER_MAX_BUFFER = + NATIVE_UI_OUTPUT_BUDGET_BYTES + NATIVE_UI_HELPER_ENVELOPE_BYTES; diff --git a/src/native/parsers/demangle.test.ts b/src/native/parsers/demangle.test.ts deleted file mode 100644 index b18d40fd0..000000000 --- a/src/native/parsers/demangle.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { expect, it } from "vitest"; -import { parseDemangledSymbols } from "./demangle.js"; - -it.each(["ordinary name ", "ordinary\t", " "])( - "preserves unchanged final symbol text: %j", - (input) => { - expect(parseDemangledSymbols([input], `${input}\n`)).toEqual([ - { input, output: input, status: "unchanged" }, - ]); - }, -); - -it.each(["carriage\r", "\r", "mid\rdle"])( - "keeps carriage returns that belong to the symbol: %j", - (input) => { - expect(parseDemangledSymbols([input], `${input}\n`)).toEqual([ - { input, output: input, status: "unchanged" }, - ]); - }, -); - -it("removes only the line terminator and preserves mixed output ordering", () => { - expect( - parseDemangledSymbols( - ["$s4main5helloyyF", "plain "], - "main.hello() -> ()\nplain \n", - ), - ).toEqual([ - { - input: "$s4main5helloyyF", - output: "main.hello() -> ()", - status: "demangled", - }, - { input: "plain ", output: "plain ", status: "unchanged" }, - ]); -}); diff --git a/src/process/DarwinProcessRunTokenReader.ts b/src/process/DarwinProcessRunTokenReader.ts new file mode 100644 index 000000000..176688063 --- /dev/null +++ b/src/process/DarwinProcessRunTokenReader.ts @@ -0,0 +1,361 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { z } from "zod"; + +import { safeParseJson } from "../domain/safeJson.js"; +import { execFileOutput } from "./ExecFileOutput.js"; +import type { + ProcessIdentityObservation, + ProcessRunTokenObservation, + ProcessTableEntry, +} from "./ProcessOwnership.js"; + +const responseSchema = z.strictObject({ + results: z.array( + z.discriminatedUnion("state", [ + z.strictObject({ + pid: z.number().int().positive(), + state: z.literal("readable"), + run_id: z.string().optional(), + reason: z.null().optional(), + }), + z.strictObject({ + pid: z.number().int().positive(), + state: z.literal("unavailable"), + run_id: z.union([z.string(), z.null()]).optional(), + reason: z.string().min(1), + }), + ]), + ), +}); +const identityResponseSchema = z.strictObject({ + results: z.array( + z.discriminatedUnion("state", [ + z.strictObject({ + pid: z.number().int().positive(), + state: z.literal("readable"), + identity: z.string().min(1), + reason: z.null().optional(), + }), + z.strictObject({ + pid: z.number().int().positive(), + state: z.literal("unavailable"), + identity: z.null().optional(), + reason: z.string().min(1), + }), + ]), + ), +}); + +const sourceFiles = [ + fileURLToPath( + new URL( + "../../bridge/process/ProcessRunTokenReader.swift", + import.meta.url, + ), + ), + fileURLToPath( + new URL( + "../../bridge/process/ProcessRunTokenReaderMain.swift", + import.meta.url, + ), + ), +]; + +/** The native Darwin process-ownership reader could not be prepared. */ +export class DarwinProcessOwnershipInspectionError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = "DarwinProcessOwnershipInspectionError"; + } +} + +interface ProcessReaderCompilation { + readonly controller: AbortController; + promise: Promise; + waiters: number; + aborting: boolean; + settled: boolean; +} + +const abortReason = (signal: AbortSignal): unknown => { + if (signal.reason !== undefined) return signal.reason; + const error = new Error("The operation was aborted"); + error.name = "AbortError"; + return error; +}; + +const isExpectedAbort = (cause: unknown, signal: AbortSignal): boolean => + signal.aborted && + (cause === signal.reason || + (cause instanceof Error && cause.name === "AbortError")); + +const waitForCompilation = async ( + promise: Promise, + signal: AbortSignal | undefined, +): Promise => { + if (signal === undefined) return promise; + if (signal.aborted) throw abortReason(signal); + return new Promise((resolve, reject) => { + let completed = false; + const finish = (callback: () => void) => { + if (completed) return; + completed = true; + signal.removeEventListener("abort", onAbort); + callback(); + }; + const onAbort = () => finish(() => reject(abortReason(signal))); + signal.addEventListener("abort", onAbort, { once: true }); + promise.then( + (value) => finish(() => resolve(value)), + (cause: unknown) => finish(() => reject(cause)), + ); + if (signal.aborted) onAbort(); + }); +}; + +/** Compile and retain the narrow Darwin sysctl reader for this REA process. */ +export const createDarwinProcessRunTokenReader = ( + options: { readonly xcrun?: string } = {}, +) => { + let root: string | undefined; + let executable: string | undefined; + let compilation: ProcessReaderCompilation | undefined; + let closePromise: Promise | undefined; + let closed = false; + let exitCleanupInstalled = false; + + const cleanupAtExit = () => { + if (root !== undefined) rmSync(root, { recursive: true, force: true }); + }; + const cleanupRoot = async (target = root): Promise => { + if (target === undefined) return; + await rm(target, { recursive: true, force: true }); + if (root !== target) return; + root = undefined; + executable = undefined; + if (exitCleanupInstalled) process.removeListener("exit", cleanupAtExit); + exitCleanupInstalled = false; + }; + const runCompilation = async (signal: AbortSignal): Promise => { + let operationRoot: string | undefined; + try { + operationRoot = await mkdtemp(join(tmpdir(), "rea-process-token-")); + root = operationRoot; + process.once("exit", cleanupAtExit); + exitCleanupInstalled = true; + if (signal.aborted || closed) throw abortReason(signal); + const output = join(operationRoot, "reader"); + try { + await execFileOutput( + options.xcrun ?? "/usr/bin/xcrun", + [ + "swiftc", + "-module-cache-path", + join(operationRoot, "modules"), + ...sourceFiles, + "-o", + output, + ], + { + timeout: 60_000, + maxBuffer: 1024 * 1024, + signal, + }, + ); + } catch (cause: unknown) { + if (isExpectedAbort(cause, signal)) throw cause; + const code = + cause instanceof Error && "code" in cause + ? String(cause.code) + : "unknown"; + throw new DarwinProcessOwnershipInspectionError( + `macOS process ownership inspection requires the Apple Swift compiler via xcrun (compiler result: ${code})`, + { cause }, + ); + } + if (signal.aborted || closed) throw abortReason(signal); + executable = output; + return output; + } catch (cause: unknown) { + const ownsRoot = operationRoot !== undefined && root === operationRoot; + if (operationRoot !== undefined) { + if (ownsRoot) await cleanupRoot(operationRoot); + else await rm(operationRoot, { recursive: true, force: true }); + } + throw cause; + } + }; + + const beginCompilation = (): ProcessReaderCompilation => { + const controller = new AbortController(); + const operation: ProcessReaderCompilation = { + controller, + promise: runCompilation(controller.signal).finally(() => { + operation.settled = true; + if (compilation === operation) compilation = undefined; + }), + waiters: 0, + aborting: false, + settled: false, + }; + compilation = operation; + return operation; + }; + + const compile = async (signal?: AbortSignal): Promise => { + if (closed) throw new Error("Darwin process token reader is closed"); + signal?.throwIfAborted(); + while (true) { + if (closed) throw new Error("Darwin process token reader is closed"); + signal?.throwIfAborted(); + if (executable !== undefined) return executable; + if (compilation === undefined && root !== undefined) { + await cleanupRoot(); + continue; + } + let operation = compilation; + if (operation?.aborting === true) { + try { + await waitForCompilation(operation.promise, signal); + } catch (cause: unknown) { + if (signal !== undefined && isExpectedAbort(cause, signal)) + throw cause; + } + continue; + } + operation ??= beginCompilation(); + operation.waiters += 1; + try { + return await waitForCompilation(operation.promise, signal); + } finally { + operation.waiters -= 1; + if ( + operation.waiters === 0 && + !operation.settled && + !operation.aborting + ) { + operation.aborting = true; + operation.controller.abort(); + await operation.promise.catch(() => undefined); + } + } + } + }; + + const read = async ( + processes: readonly ProcessTableEntry[], + ): Promise> => { + if (processes.length === 0) return new Map(); + const binary = await compile(); + const pids = processes.map(({ pid }) => String(pid)); + const { stdout } = await execFileOutput(binary, pids, { timeout: 15_000 }); + const decoded = safeParseJson(stdout); + if (!decoded.ok) + throw new Error( + `macOS process token helper returned invalid JSON: ${decoded.error}`, + { cause: decoded.cause }, + ); + const parsed = responseSchema.safeParse(decoded.value); + if (!parsed.success) + throw new Error( + "macOS process token helper returned an invalid response", + { + cause: parsed.error, + }, + ); + const requested = new Set(processes.map(({ pid }) => pid)); + const observations = new Map(); + for (const result of parsed.data.results) { + if (!requested.has(result.pid) || observations.has(result.pid)) + throw new Error( + "macOS process token helper returned an unexpected PID set", + ); + observations.set( + result.pid, + result.state === "readable" + ? { state: "readable", runId: result.run_id } + : { + state: "unavailable", + reason: + result.reason ?? "process token observation is unavailable", + }, + ); + } + if (observations.size !== requested.size) + throw new Error( + "macOS process token helper returned an incomplete PID set", + ); + return observations; + }; + + const identities = async ( + processes: readonly ProcessTableEntry[], + signal?: AbortSignal, + ): Promise> => { + if (processes.length === 0) return new Map(); + const binary = await compile(signal); + const pids = processes.map(({ pid }) => String(pid)); + const { stdout } = await execFileOutput(binary, ["--identities", ...pids], { + timeout: 15_000, + ...(signal === undefined ? {} : { signal }), + }); + const decoded = safeParseJson(stdout); + if (!decoded.ok) + throw new Error( + `macOS process identity helper returned invalid JSON: ${decoded.error}`, + { cause: decoded.cause }, + ); + const parsed = identityResponseSchema.safeParse(decoded.value); + if (!parsed.success) + throw new Error( + "macOS process identity helper returned an invalid response", + { + cause: parsed.error, + }, + ); + const requested = new Set(processes.map(({ pid }) => pid)); + const observations = new Map(); + for (const result of parsed.data.results) { + if (!requested.has(result.pid) || observations.has(result.pid)) + throw new Error( + "macOS process identity helper returned an unexpected PID set", + ); + observations.set( + result.pid, + result.state === "readable" + ? { state: "readable", identity: result.identity } + : { state: "unavailable", reason: result.reason }, + ); + } + if (observations.size !== requested.size) + throw new Error( + "macOS process identity helper returned an incomplete PID set", + ); + return observations; + }; + + const close = (): Promise => { + if (closePromise !== undefined) return closePromise; + closed = true; + const pendingClose = (async () => { + const operation = compilation; + if (operation !== undefined && !operation.settled) { + operation.aborting = true; + operation.controller.abort(); + await operation.promise.catch(() => undefined); + } + await cleanupRoot(); + })().catch((cause: unknown) => { + if (closePromise === pendingClose) closePromise = undefined; + throw cause; + }); + closePromise = pendingClose; + return closePromise; + }; + + return { read, identities, prepare: compile, close }; +}; diff --git a/src/process/ExecFileOutput.test.ts b/src/process/ExecFileOutput.test.ts index 1dd81155c..bb963987b 100644 --- a/src/process/ExecFileOutput.test.ts +++ b/src/process/ExecFileOutput.test.ts @@ -23,4 +23,13 @@ describe("execFileOutput", () => { ), ).rejects.toMatchObject({ code: "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" }); }); + + it("retains both captured streams on a failed child process", async () => { + await expect( + execFileOutput(process.execPath, [ + "-e", + 'process.stdout.write("out"); process.stderr.write("err"); process.exitCode = 7;', + ]), + ).rejects.toMatchObject({ code: 7, stdout: "out", stderr: "err" }); + }); }); diff --git a/src/process/ExecFileOutput.ts b/src/process/ExecFileOutput.ts index 53360848a..232a93a7c 100644 --- a/src/process/ExecFileOutput.ts +++ b/src/process/ExecFileOutput.ts @@ -25,6 +25,8 @@ export const execFileOutput = ( }, (error, stdout, stderr) => { if (error !== null) { + Reflect.set(error, "stdout", stdout); + Reflect.set(error, "stderr", stderr); reject(error); return; } diff --git a/src/process/OwnedCommand.ts b/src/process/OwnedCommand.ts new file mode 100644 index 000000000..1fa054c70 --- /dev/null +++ b/src/process/OwnedCommand.ts @@ -0,0 +1,198 @@ +import type { + OwnedProviderProcessSpawnOptions, + ProviderProcessSnapshot, + SpawnedOwnedProviderProcess, +} from "./ProviderProcess.js"; +import { + ProviderProcessSupervisor, + spawnOwnedProviderProcess, +} from "./ProviderProcess.js"; +import { cleanupOwnedProcessGroup } from "./ProcessOwnership.js"; +import { + ProviderStartupDeadline, + waitForAbortableDelay, +} from "./ProviderDeadline.js"; + +/** A short-lived command failed its lifecycle or complete-output contract. */ +export class OwnedCommandFailure extends Error { + constructor( + readonly reason: "cancelled" | "timeout" | "output-limit" | "process", + message: string, + readonly snapshot: ProviderProcessSnapshot | null = null, + readonly cleanupFailure: string | null = null, + options?: ErrorOptions, + readonly resources: readonly string[] = [], + ) { + super(message, options); + } +} + +/** Execute a single owned process with independent cleanup and bounded diagnostic retention. */ +export const runOwnedCommand = async ( + spawn: OwnedProviderProcessSpawnOptions, + limits: { readonly timeoutMs: number; readonly diagnosticBytes: number }, + options: { + readonly signal?: AbortSignal; + readonly launcher?: ( + input: OwnedProviderProcessSpawnOptions, + ) => Promise; + } = {}, +): Promise => { + const callerSignal = options.signal ?? spawn.signal; + if (callerSignal?.aborted) + throw new OwnedCommandFailure( + "cancelled", + "Command cancelled before launch.", + ); + const deadline = new ProviderStartupDeadline(limits.timeoutMs, callerSignal); + try { + return await collectOwnedCommand( + spawn, + limits, + options.launcher ?? spawnOwnedProviderProcess, + deadline, + callerSignal, + ); + } finally { + deadline.dispose(); + } +}; + +const collectOwnedCommand = async ( + spawn: OwnedProviderProcessSpawnOptions, + limits: { readonly diagnosticBytes: number }, + launcher: ( + input: OwnedProviderProcessSpawnOptions, + ) => Promise, + deadline: ProviderStartupDeadline, + callerSignal?: AbortSignal, +): Promise => { + const signal = deadline.signal; + let launched: SpawnedOwnedProviderProcess; + try { + launched = await launcher({ + ...spawn, + signal, + }); + } catch (cause: unknown) { + const interruption = deadline.interruption; + if (interruption !== undefined) + throw new OwnedCommandFailure( + interruption, + interruption === "cancelled" + ? "Command cancelled during ownership preparation." + : "Command deadline elapsed during ownership preparation.", + null, + null, + { cause }, + ); + throw cause; + } + let exceeded = false; + let processFailure: string | undefined; + const supervisor = new ProviderProcessSupervisor( + { + ...launched, + ownsProcessLifetime: true, + cleanup: + launched.cleanup ?? + (() => cleanupOwnedProcessGroup(launched.ownership)), + }, + { + maxDiagnosticBytes: limits.diagnosticBytes, + onDiagnostic: (event) => { + if (event.type === "output" && event.truncated === true) + exceeded = true; + if (event.type === "error") processFailure = event.message; + }, + }, + ); + let failure: OwnedCommandFailure | undefined; + try { + while (!(await supervisor.waitForOutputClose(10))) { + const interruption = deadline.interruption; + if (interruption !== undefined) + throw new OwnedCommandFailure( + interruption, + interruption === "cancelled" + ? "Command cancelled." + : "Command deadline elapsed.", + ); + if (exceeded) + throw new OwnedCommandFailure( + "output-limit", + "Command diagnostic output exceeded its complete-output budget.", + ); + if (processFailure !== undefined) + throw new OwnedCommandFailure("process", processFailure); + await waitForAbortableDelay(25, signal); + } + const snapshot = supervisor.snapshot(); + const interruption = deadline.interruption; + if (interruption !== undefined) + throw new OwnedCommandFailure( + interruption, + interruption === "cancelled" + ? "Command cancelled." + : "Command deadline elapsed.", + snapshot, + ); + if (exceeded) + throw new OwnedCommandFailure( + "output-limit", + "Command diagnostic output exceeded its complete-output budget.", + snapshot, + ); + if ( + processFailure !== undefined || + snapshot.exitCode !== 0 || + snapshot.signal !== null + ) + throw new OwnedCommandFailure( + "process", + processFailure ?? + "Command failed; inspect its exit status and diagnostics.", + snapshot, + ); + } catch (cause: unknown) { + failure = + cause instanceof OwnedCommandFailure + ? cause + : new OwnedCommandFailure( + "process", + "Command collection failed.", + null, + null, + { cause }, + ); + } + const stopped = await supervisor.stop(); + const snapshot = supervisor.snapshot(); + supervisor.dispose(); + if (stopped.status === "incomplete") + throw new OwnedCommandFailure( + failure?.reason ?? "process", + failure?.message ?? "Command cleanup failed.", + snapshot, + stopped.reason, + failure === undefined ? undefined : { cause: failure }, + [ + spawn.runId, + ...(spawn.cwd === undefined ? [] : [spawn.cwd]), + `pid:${launched.ownership.leaderPid}`, + `process-group:${launched.ownership.processGroupId}`, + ], + ); + if (failure !== undefined) + throw new OwnedCommandFailure( + failure.reason, + failure.message, + snapshot, + failure.cleanupFailure, + { cause: failure }, + failure.resources, + ); + if (callerSignal?.aborted) + throw new OwnedCommandFailure("cancelled", "Command cancelled.", snapshot); + return snapshot; +}; diff --git a/src/process/ProcessOwnership.identity.test.ts b/src/process/ProcessOwnership.identity.test.ts new file mode 100644 index 000000000..406a35e33 --- /dev/null +++ b/src/process/ProcessOwnership.identity.test.ts @@ -0,0 +1,481 @@ +import { describe, expect, it, vi } from "vitest"; +import { + cleanupOwnedProcessGroup, + verifyNoTokenOwnedProcesses, + type ProcessOwnershipHost, + type ProcessTableEntry, +} from "./ProcessOwnership.js"; +import { + createSystemProcessOwnershipHost, + parseLinuxProcessStartTime, +} from "./ProcessOwnershipObservation.js"; + +const itOnLinux = process.platform === "linux" ? it : it.skip; + +describe("Linux process start identities", () => { + itOnLinux("reads Linux process start identities from proc stat", async () => { + const host = createSystemProcessOwnershipHost("linux", {}); + expect(host.processIdentities).toBeDefined(); + expect(host.captureBaseline).toBeDefined(); + const observedProcess = { + pid: process.pid, + parentPid: process.ppid, + processGroupId: process.pid, + state: "S", + command: process.execPath, + } satisfies ProcessTableEntry; + const identities = await host.processIdentities?.([observedProcess]); + expect(identities?.get(observedProcess.pid)).toMatchObject({ + state: "readable", + identity: expect.stringMatching(/^linux-starttime:\d+$/u), + }); + const baseline = await host.captureBaseline?.(); + expect(baseline).toContainEqual({ + pid: observedProcess.pid, + identity: expect.stringMatching(/^linux-starttime:\d+$/u), + }); + }); + + it("parses stat start time after a command name containing parentheses", () => { + const fields = Array.from({ length: 20 }, (_, index) => + index === 0 ? "S" : index === 19 ? "987654" : "0", + ); + expect( + parseLinuxProcessStartTime( + `42 (worker ) old ) name) ${fields.join(" ")}`, + ), + ).toBe("987654"); + expect( + parseLinuxProcessStartTime("42 (worker) S too-short"), + ).toBeUndefined(); + }); +}); + +describe("capture-local process ownership", () => { + it("validates and cleans an exact-token owner without a capture baseline", async () => { + const process: ProcessTableEntry = { + pid: 904, + parentPid: 1, + processGroupId: 904, + state: "S", + command: "generic-darwin-owner", + }; + const signalGroup = vi.fn(); + const host: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve([process]), + environment: () => Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }), + runTokens: (entries) => + Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", runId: "run-token" } as const, + ]), + ), + ), + processIdentities: (entries) => + Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", identity: "owner-start" } as const, + ]), + ), + ), + signalGroup, + }; + + await expect( + cleanupOwnedProcessGroup( + { + runId: "run-token", + leaderPid: process.pid, + processGroupId: process.processGroupId, + sweepTokenOwnedProcesses: true, + }, + host, + ), + ).resolves.toEqual({ cleaned: true, signaled: true }); + expect(signalGroup).toHaveBeenCalledWith(process.processGroupId, "SIGKILL"); + }); + + it("cleans verified owners before reporting an unrelated opaque process", async () => { + const owned: ProcessTableEntry = { + pid: 905, + parentPid: 1, + processGroupId: 905, + state: "S", + command: "owned-detached-child", + }; + const opaque: ProcessTableEntry = { + pid: 906, + parentPid: 1, + processGroupId: 906, + state: "S", + command: "unrelated-process", + }; + const malformed: ProcessTableEntry = { + ...opaque, + pid: 907, + processGroupId: 907, + command: "malformed-procargs-process", + }; + const sysctlDenied: ProcessTableEntry = { + ...opaque, + pid: 908, + processGroupId: 908, + command: "sysctl-denied-process", + }; + const signalGroup = vi.fn(); + const host: ProcessOwnershipHost = { + listProcesses: () => + Promise.resolve([owned, opaque, malformed, sysctlDenied]), + environment: (pid) => + pid === owned.pid + ? Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }) + : Promise.reject(new Error("permission denied")), + runTokens: (entries) => + Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + pid === owned.pid + ? { state: "readable", runId: "run-token" as const } + : { + state: "unavailable", + reason: + pid === malformed.pid + ? ("malformed_procargs" as const) + : pid === sysctlDenied.pid + ? ("sysctl_failed_1" as const) + : ("environment_unavailable" as const), + }, + ]), + ), + ), + signalGroup, + }; + + await expect( + cleanupOwnedProcessGroup( + { + runId: "run-token", + leaderPid: 100, + processGroupId: 100, + sweepTokenOwnedProcesses: true, + }, + host, + ), + ).resolves.toMatchObject({ + cleaned: false, + reason: + "process ownership token could not be read for 3 live process(es): environment_unavailable=1, malformed_procargs=1, sysctl_errno_1=1", + }); + expect(signalGroup).toHaveBeenCalledWith(owned.processGroupId, "SIGKILL"); + expect(signalGroup).not.toHaveBeenCalledWith( + opaque.processGroupId, + "SIGKILL", + ); + expect(signalGroup).not.toHaveBeenCalledWith( + malformed.processGroupId, + "SIGKILL", + ); + expect(signalGroup).not.toHaveBeenCalledWith( + sysctlDenied.processGroupId, + "SIGKILL", + ); + }); +}); + +describe("capture-local process identity baselines", () => { + it("checks reused and unknown PIDs while skipping unchanged identities", async () => { + const tokenReads: number[][] = []; + const processes: readonly ProcessTableEntry[] = [ + { + pid: 899, + parentPid: 1, + processGroupId: 899, + state: "S", + command: "stable", + }, + { + pid: 900, + parentPid: 1, + processGroupId: 900, + state: "S", + command: "reused", + }, + { + pid: 902, + parentPid: 1, + processGroupId: 902, + state: "S", + command: "unknown", + }, + ]; + const host: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve(processes), + environment: () => Promise.resolve({}), + processIdentities: (entries) => + Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + pid === 902 + ? { state: "unavailable", reason: "fixture" } + : { + state: "readable", + identity: pid === 899 ? "stable-start" : "reused-start", + }, + ]), + ), + ), + runTokens: (entries) => { + tokenReads.push(entries.map(({ pid }) => pid)); + return Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { + state: "readable", + runId: pid === 900 ? "run-token" : undefined, + }, + ]), + ), + ); + }, + signalGroup: vi.fn(), + }; + + await expect( + verifyNoTokenOwnedProcesses("run-token", host, [ + { pid: 899, identity: "stable-start" }, + { pid: 900, identity: "old-start" }, + { pid: 902, identity: null }, + ]), + ).resolves.toEqual({ + cleaned: false, + reason: "process identity could not be revalidated", + failures: [ + { + pid: 902, + reason: "process-identity-unavailable", + diagnostic: "process identity was unavailable during token scan", + }, + ], + }); + expect(tokenReads).toEqual([[900]]); + }); + + it("revalidates fallback environment owners before signaling after PID reuse", async () => { + let identityReads = 0; + const process: ProcessTableEntry = { + pid: 910, + parentPid: 1, + processGroupId: 910, + state: "S", + command: "fallback-environment-owner", + }; + const signalGroup = vi.fn(); + const host: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve([process]), + environment: () => Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }), + processIdentities: (entries) => { + identityReads += 1; + const identity = identityReads < 3 ? "owned-start" : "reused-start"; + return Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", identity } as const, + ]), + ), + ); + }, + signalGroup, + }; + + await expect( + cleanupOwnedProcessGroup( + { + runId: "run-token", + leaderPid: 100, + processGroupId: 100, + sweepTokenOwnedProcesses: true, + captureBaseline: [{ pid: process.pid, identity: "old-start" }], + }, + host, + ), + ).resolves.toMatchObject({ + cleaned: false, + reason: "process identity could not be revalidated", + }); + expect(signalGroup).not.toHaveBeenCalled(); + expect(identityReads).toBeGreaterThanOrEqual(3); + }); +}); + +describe("process identity during token validation", () => { + it("revalidates identity after the final environment token read", async () => { + let environmentReads = 0; + let reusedDuringFinalRead = false; + const process: ProcessTableEntry = { + pid: 912, + parentPid: 1, + processGroupId: 912, + state: "S", + command: "reused-during-final-token-read", + }; + const signalGroup = vi.fn(); + const host: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve([process]), + environment: () => { + environmentReads += 1; + if (environmentReads === 3) reusedDuringFinalRead = true; + return Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }); + }, + processIdentities: (entries) => { + const identity = reusedDuringFinalRead ? "reused-start" : "owned-start"; + return Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", identity } as const, + ]), + ), + ); + }, + signalGroup, + }; + + await expect( + cleanupOwnedProcessGroup( + { + runId: "run-token", + leaderPid: 100, + processGroupId: 100, + sweepTokenOwnedProcesses: true, + captureBaseline: [{ pid: process.pid, identity: "old-start" }], + }, + host, + ), + ).resolves.toMatchObject({ + cleaned: false, + reason: "process identity could not be revalidated", + }); + expect(signalGroup).not.toHaveBeenCalled(); + expect(reusedDuringFinalRead).toBe(true); + }); + + it("fails closed when a candidate PID changes identity during token reading", async () => { + let identityReads = 0; + const process: ProcessTableEntry = { + pid: 911, + parentPid: 1, + processGroupId: 911, + state: "S", + command: "reused-during-token-read", + }; + const host: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve([process]), + environment: () => Promise.resolve({}), + processIdentities: (entries) => { + identityReads += 1; + const identity = identityReads === 1 ? "new-start" : "reused-start"; + return Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", identity } as const, + ]), + ), + ); + }, + runTokens: (entries) => + Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", runId: undefined } as const, + ]), + ), + ), + signalGroup: vi.fn(), + }; + + await expect( + verifyNoTokenOwnedProcesses("run-token", host, [ + { pid: process.pid, identity: "old-start" }, + ]), + ).resolves.toEqual({ + cleaned: false, + reason: "process identity could not be revalidated", + failures: [ + { + pid: process.pid, + reason: "process-identity-unavailable", + diagnostic: + "process identity changed or became unavailable during token validation", + }, + ], + }); + }); +}); + +describe("process identity before signaling", () => { + it("refuses to signal when PID identity changes after token validation", async () => { + const signalGroup = vi.fn(); + let identityReads = 0; + const process: ProcessTableEntry = { + pid: 903, + parentPid: 1, + processGroupId: 903, + state: "S", + command: "reused-before-signal", + }; + const host: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve([process]), + environment: () => Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }), + processIdentities: (entries) => { + identityReads += 1; + const identity = identityReads < 3 ? "owned-start" : "reused-start"; + return Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", identity } as const, + ]), + ), + ); + }, + runTokens: (entries) => + Promise.resolve( + new Map( + entries.map(({ pid }) => [ + pid, + { state: "readable", runId: "run-token" } as const, + ]), + ), + ), + signalGroup, + }; + + await expect( + cleanupOwnedProcessGroup( + { + runId: "run-token", + leaderPid: 100, + processGroupId: 100, + sweepTokenOwnedProcesses: true, + captureBaseline: [{ pid: process.pid, identity: "old-start" }], + }, + host, + ), + ).resolves.toMatchObject({ + cleaned: false, + reason: "process identity could not be revalidated", + }); + expect(signalGroup).not.toHaveBeenCalled(); + expect(identityReads).toBeGreaterThanOrEqual(3); + }); +}); diff --git a/src/process/ProcessOwnership.part2.test.ts b/src/process/ProcessOwnership.part2.test.ts index 7e6e81b4c..ed6552bda 100644 --- a/src/process/ProcessOwnership.part2.test.ts +++ b/src/process/ProcessOwnership.part2.test.ts @@ -5,36 +5,7 @@ import { type ProcessOwnershipHost, } from "./ProcessOwnership.js"; import { observeOwnedProcessLineage } from "./ProcessOwnershipObservation.js"; -const ownership = { - runId: "run-token", - leaderPid: 100, - processGroupId: 100, -}; -const host = ( - environments: Readonly>>>, -): { - readonly adapter: ProcessOwnershipHost; - readonly signalGroup: ReturnType; -} => { - const signalGroup = vi.fn(); - return { - adapter: { - listProcesses: () => - Promise.resolve( - Object.keys(environments).map((pid) => ({ - pid: Number(pid), - parentPid: Number(pid) === 100 ? 1 : 100, - processGroupId: 100, - state: "S", - command: "fixture", - })), - ), - environment: (pid) => Promise.resolve(environments[pid] ?? {}), - signalGroup, - }, - signalGroup, - }; -}; +import { host, ownership } from "./ProcessOwnership.fixture.js"; describe("owned process-group cleanup discovery", () => { it("signals token-owned groups that were reparented outside the launcher tree", async () => { const signalGroup = vi.fn(); @@ -209,3 +180,38 @@ describe("owned process-group cleanup discovery", () => { }); }); }); + +describe("process environment permission diagnostics", () => { + it.each(["EACCES", "EPERM"])( + "preserves %s while refusing to signal an unverifiable live process", + async (code) => { + const signalGroup = vi.fn(); + const diagnostic = `${code}: permission denied, open '/proc/900/environ'`; + const adapter: ProcessOwnershipHost = { + listProcesses: () => + Promise.resolve([ + { + pid: 900, + parentPid: 1, + processGroupId: 900, + state: "S", + command: "unverifiable-process", + }, + ]), + environment: () => Promise.reject(new Error(diagnostic)), + signalGroup, + }; + await expect( + cleanupOwnedProcessGroup( + { ...ownership, sweepTokenOwnedProcesses: true }, + adapter, + ), + ).resolves.toMatchObject({ + cleaned: false, + reason: `process ownership token could not be read for 1 live process(es): environment_errno_${code}=1`, + failures: [{ pid: 900, reason: "environment-unreadable", diagnostic }], + }); + expect(signalGroup).not.toHaveBeenCalled(); + }, + ); +}); diff --git a/src/process/ProcessOwnership.test.ts b/src/process/ProcessOwnership.test.ts index d565f0494..815d9188c 100644 --- a/src/process/ProcessOwnership.test.ts +++ b/src/process/ProcessOwnership.test.ts @@ -6,36 +6,7 @@ import { import { parseProcessEnvironment } from "./ProcessOwnershipObservation.js"; import { selectCapturedProcessGroupIds } from "./ProcessOwnershipProcessTree.js"; import { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js"; -const ownership = { - runId: "run-token", - leaderPid: 100, - processGroupId: 100, -}; -const host = ( - environments: Readonly>>>, -): { - readonly adapter: ProcessOwnershipHost; - readonly signalGroup: ReturnType; -} => { - const signalGroup = vi.fn(); - return { - adapter: { - listProcesses: () => - Promise.resolve( - Object.keys(environments).map((pid) => ({ - pid: Number(pid), - parentPid: Number(pid) === 100 ? 1 : 100, - processGroupId: 100, - state: "S", - command: "fixture", - })), - ), - environment: (pid) => Promise.resolve(environments[pid] ?? {}), - signalGroup, - }, - signalGroup, - }; -}; +import { host, ownership } from "./ProcessOwnership.fixture.js"; describe("owned process-group cleanup discovery", () => { it("excludes sampled groups whose leader was outside the captured tree", () => { expect( @@ -93,6 +64,177 @@ describe("owned process-group cleanup discovery", () => { }); expect(signalGroup).toHaveBeenCalledWith(100, "SIGKILL"); }); +}); + +describe("opaque process neighbors during cleanup", () => { + it("cleans verified run-owned groups while reporting an opaque neighbor", async () => { + const processes = [ + { pid: 100, parentPid: 1, processGroupId: 100, command: "capture" }, + { + pid: 200, + parentPid: 1, + processGroupId: 200, + command: "detached-child", + }, + { + pid: 900, + parentPid: 1, + processGroupId: 900, + command: "opaque-neighbor", + }, + ].map((process) => ({ ...process, state: "S" })); + const signalGroup = vi.fn(); + const identities = new Map( + processes.map(({ pid }) => [ + pid, + { state: "readable" as const, identity: `start-${String(pid)}` }, + ]), + ); + const adapter: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve(processes), + environment: (pid) => + Promise.resolve({ + REA_PROCESS_RUN_ID: pid === 900 ? "unowned" : "run-token", + }), + processIdentities: () => Promise.resolve(identities), + runTokens: (members) => + Promise.resolve( + new Map( + members.map(({ pid }) => [ + pid, + pid === 900 + ? { + state: "unavailable" as const, + reason: "environment_unavailable", + } + : { state: "readable" as const, runId: "run-token" }, + ]), + ), + ), + signalGroup, + }; + + const result = await cleanupOwnedProcessGroup( + { + ...ownership, + sweepTokenOwnedProcesses: true, + captureBaseline: [], + }, + adapter, + ); + + expect(result).toMatchObject({ + cleaned: false, + reason: expect.stringContaining("environment_unavailable=1"), + }); + expect(signalGroup.mock.calls).toEqual([ + [100, "SIGKILL"], + [200, "SIGKILL"], + ]); + expect(signalGroup).not.toHaveBeenCalledWith(900, "SIGKILL"); + }); +}); + +describe("sampled detached process groups during cleanup", () => { + it("signals only token-authenticated sampled groups and reports an unowned sample", async () => { + const processes = [ + { + pid: 100, + parentPid: 1, + processGroupId: 100, + state: "S", + command: "capture", + }, + { + pid: 200, + parentPid: 1, + processGroupId: 200, + state: "S", + command: "sanitized-detached-child", + }, + ]; + const signalGroup = vi.fn(); + const identities = new Map([ + [100, { state: "readable" as const, identity: "start-100" }], + [200, { state: "readable" as const, identity: "start-200" }], + ]); + const adapter: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve(processes), + environment: (pid) => + Promise.resolve(pid === 100 ? { REA_PROCESS_RUN_ID: "run-token" } : {}), + processIdentities: () => Promise.resolve(identities), + runTokens: (members) => + Promise.resolve( + new Map( + members.map(({ pid }) => [ + pid, + { + state: "readable" as const, + runId: pid === 100 ? "run-token" : undefined, + }, + ]), + ), + ), + signalGroup, + }; + + const result = await cleanupOwnedProcessGroup( + { + ...ownership, + sweepTokenOwnedProcesses: true, + sampledProcessGroupIds: [200], + }, + adapter, + ); + + expect(result).toMatchObject({ + cleaned: false, + reason: "process tree contains an unowned or PID-reused process", + failures: [{ pid: 200, reason: "run-token-mismatch" }], + }); + expect(signalGroup).toHaveBeenCalledTimes(1); + expect(signalGroup).toHaveBeenCalledWith(100, "SIGKILL"); + expect(signalGroup).not.toHaveBeenCalledWith(200, "SIGKILL"); + }); + + it("reports a live sampled group after the launcher has exited without signaling it", async () => { + const process = { + pid: 200, + parentPid: 1, + processGroupId: 200, + state: "S", + command: "sanitized-detached-child", + }; + const signalGroup = vi.fn(); + const adapter: ProcessOwnershipHost = { + listProcesses: () => Promise.resolve([process]), + environment: () => Promise.resolve({}), + runTokens: () => + Promise.resolve( + new Map([[200, { state: "readable" as const, runId: undefined }]]), + ), + signalGroup, + }; + + const result = await cleanupOwnedProcessGroup( + { + ...ownership, + sweepTokenOwnedProcesses: true, + sampledProcessGroupIds: [200], + }, + adapter, + ); + + expect(result).toMatchObject({ + cleaned: false, + reason: "process tree contains an unowned or PID-reused process", + failures: [{ pid: 200, reason: "run-token-mismatch" }], + }); + expect(signalGroup).not.toHaveBeenCalled(); + }); +}); + +describe("rooted process-group cleanup", () => { it("validates and signals rooted descendant groups once, root first", async () => { const processes = [ { pid: 100, parentPid: 1, processGroupId: 100 }, diff --git a/src/process/ProcessOwnership.ts b/src/process/ProcessOwnership.ts index d74961401..814009fe3 100644 --- a/src/process/ProcessOwnership.ts +++ b/src/process/ProcessOwnership.ts @@ -2,8 +2,8 @@ import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { - createSystemProcessOwnershipHost, errorMessage, + systemProcessOwnershipHost as systemHost, } from "./ProcessOwnershipObservation.js"; import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js"; import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js"; @@ -21,8 +21,21 @@ export interface OwnedProcessGroup { readonly expectedParentPid?: number; /** Scan all live processes for this run token during cleanup. */ readonly sweepTokenOwnedProcesses?: boolean; + /** Prelaunch process identities for this capture; absent on generic callers. */ + readonly captureBaseline?: ProcessOwnershipBaseline; + /** + * Groups observed in capture samples. Observation adds cleanup candidates but + * does not authorize signaling; every live member still needs token proof. + */ + readonly sampledProcessGroupIds?: readonly number[]; } +/** Capture-local baseline; null means the PID identity could not be established. */ +export type ProcessOwnershipBaseline = readonly { + readonly pid: number; + readonly identity: string | null; +}[]; + /** One entry from an operating-system process-table snapshot. */ export interface ProcessTableEntry { readonly pid: number; @@ -32,6 +45,11 @@ export interface ProcessTableEntry { readonly command: string; } +/** Native process start identity; unavailable identities must never be skipped. */ +export type ProcessIdentityObservation = + | { readonly state: "readable"; readonly identity: string } + | { readonly state: "unavailable"; readonly reason: string }; + /** Token-verified process lineage retained for one owned provider run. */ export interface OwnedProcessLineage { readonly runId: string; @@ -64,11 +82,32 @@ export type ProcessLineageObservation = /** Narrow operating-system seam used to inspect processes and signal groups. */ export interface ProcessOwnershipHost { readonly platform?: NodeJS.Platform; - listProcesses(): Promise; + /** Prepare required native identity inspection before launching a child. */ + prepare?(signal?: AbortSignal): Promise; + /** Release owned native inspection helpers and their temporary files. */ + close?(): Promise; + /** List current processes; startup inspection may be cancelled by the caller. */ + listProcesses(signal?: AbortSignal): Promise; environment(pid: number): Promise>>; + /** Read only run-token values in one host operation when the OS supports it. */ + runTokens?( + processes: readonly ProcessTableEntry[], + ): Promise>; + /** Read stable identities; omit the signal during cleanup so it can finish. */ + processIdentities?( + processes: readonly ProcessTableEntry[], + signal?: AbortSignal, + ): Promise>; + /** Snapshot identities before launch, honoring startup cancellation. */ + captureBaseline?(signal?: AbortSignal): Promise; signalGroup(processGroupId: number, signal: NodeJS.Signals): void; } +/** Whether one live process exposed its ownership token to the host. */ +export type ProcessRunTokenObservation = + | { readonly state: "readable"; readonly runId: string | undefined } + | { readonly state: "unavailable"; readonly reason: string }; + /** Narrow Windows P0 seam for bounded process-tree termination. */ export interface WindowsProcessTreeHost { terminateTree(rootPid: number): Promise<"terminated" | "missing">; @@ -77,7 +116,10 @@ export interface WindowsProcessTreeHost { /** Per-member reason that token-verified cleanup failed closed. */ interface ProcessOwnershipValidationFailure { readonly pid: number; - readonly reason: "environment-unreadable" | "run-token-mismatch"; + readonly reason: + | "environment-unreadable" + | "run-token-mismatch" + | "process-identity-unavailable"; readonly diagnostic?: string; } @@ -89,7 +131,10 @@ export type ProcessCleanupResult = readonly reason: string; readonly failures?: readonly { readonly pid: number; - readonly reason: "environment-unreadable" | "run-token-mismatch"; + readonly reason: + | "environment-unreadable" + | "run-token-mismatch" + | "process-identity-unavailable"; readonly diagnostic?: string; }[]; }; @@ -128,8 +173,6 @@ export const cleanupWindowsProcessTree = async ( } }; -const systemHost = createSystemProcessOwnershipHost(); - const systemWindowsProcessTreeHost: WindowsProcessTreeHost = { async terminateTree(rootPid) { try { @@ -181,6 +224,7 @@ export const cleanupOwnedProcessGroup = async ( processGroupId, ownership, host, + plan.tokenOwnedIdentities, ); if ("cleaned" in revalidation) return revalidation; if (revalidation.empty) continue; @@ -197,6 +241,8 @@ export const cleanupOwnedProcessGroup = async ( }; } } + if (plan.unresolved.length > 0) + return cleanupValidationFailure(plan.unresolved); return { cleaned: true, signaled }; }; @@ -204,6 +250,7 @@ export const cleanupOwnedProcessGroup = async ( export const verifyNoTokenOwnedProcesses = async ( runId: string, host: ProcessOwnershipHost = systemHost, + captureBaseline?: ProcessOwnershipBaseline, ): Promise => { const processTable = await readLiveProcessTable(host); if (!processTable.available) @@ -215,6 +262,7 @@ export const verifyNoTokenOwnedProcesses = async ( processTable.processes, runId, host, + captureBaseline, ); if (scan.failures.length > 0) return cleanupValidationFailure(scan.failures); return scan.owned.length === 0 @@ -243,6 +291,8 @@ const readLiveProcessTable = async ( interface OwnedCleanupPlan { readonly signalOrder: readonly number[]; + readonly tokenOwnedIdentities: ReadonlyMap; + readonly unresolved: readonly ProcessOwnershipValidationFailure[]; } const createOwnedCleanupPlan = async ( @@ -250,12 +300,21 @@ const createOwnedCleanupPlan = async ( processes: readonly ProcessTableEntry[], host: ProcessOwnershipHost, ): Promise => { + const sampledProcessGroupIds = new Set( + (ownership.sampledProcessGroupIds ?? []).filter( + (processGroupId) => + Number.isSafeInteger(processGroupId) && processGroupId > 0, + ), + ); const tokenOwned = ownership.sweepTokenOwnedProcesses === true - ? await scanTokenOwnedProcesses(processes, ownership.runId, host) - : { owned: [], failures: [] }; - if (tokenOwned.failures.length > 0) - return cleanupValidationFailure(tokenOwned.failures); + ? await scanTokenOwnedProcesses( + processes, + ownership.runId, + host, + ownership.captureBaseline, + ) + : { owned: [], failures: [], identities: new Map() }; const tokenOwnedGroupIds = new Set( tokenOwned.owned.map(({ processGroupId }) => processGroupId), ); @@ -266,9 +325,18 @@ const createOwnedCleanupPlan = async ( if ( launcher === undefined && rootMembers.length === 0 && - tokenOwned.owned.length === 0 + tokenOwned.owned.length === 0 && + !processes.some(({ processGroupId }) => + sampledProcessGroupIds.has(processGroupId), + ) ) - return { cleaned: true, signaled: false }; + return tokenOwned.failures.length > 0 + ? { + signalOrder: [], + tokenOwnedIdentities: tokenOwned.identities, + unresolved: tokenOwned.failures, + } + : { cleaned: true, signaled: false }; let descendants: readonly ProcessTableEntry[] = []; if (launcher !== undefined) { const identityFailure = launcherIdentityFailure(launcher, ownership); @@ -282,6 +350,11 @@ const createOwnedCleanupPlan = async ( processGroupIds.add(descendant.processGroupId); for (const process of tokenOwned.owned) processGroupIds.add(process.processGroupId); + for (const processGroupId of sampledProcessGroupIds) + processGroupIds.add(processGroupId); + const signalableGroups = new Set(); + const unresolved = [...tokenOwned.failures]; + const unverifiableSampledGroups = new Set(); for (const processGroupId of processGroupIds) { if (processGroupId === ownership.processGroupId) continue; const groupLeader = processes.find( @@ -290,29 +363,69 @@ const createOwnedCleanupPlan = async ( ); if ( (groupLeader === undefined || !descendantPids.has(groupLeader.pid)) && - !tokenOwnedGroupIds.has(processGroupId) + !tokenOwnedGroupIds.has(processGroupId) && + !sampledProcessGroupIds.has(processGroupId) ) return { cleaned: false, reason: "descendant process-group leader identity could not be verified", }; + if ( + (groupLeader === undefined || !descendantPids.has(groupLeader.pid)) && + !tokenOwnedGroupIds.has(processGroupId) + ) + unverifiableSampledGroups.add(processGroupId); } const liveMembers = processes.filter(({ processGroupId }) => processGroupIds.has(processGroupId), ); - const failures = await processOwnershipFailures( - liveMembers, - ownership.runId, - host, - ); - if (failures.length > 0) return cleanupValidationFailure(failures); + for (const processGroupId of processGroupIds) { + const members = liveMembers.filter( + ({ processGroupId: observedGroupId }) => + observedGroupId === processGroupId, + ); + if (members.length === 0) continue; + const failures = await processOwnershipFailures( + members, + ownership.runId, + host, + tokenOwned.identities, + ); + if (failures.length === 0 && unverifiableSampledGroups.has(processGroupId)) + unresolved.push( + ...members.map(({ pid }) => ({ + pid, + reason: "process-identity-unavailable" as const, + diagnostic: + "sampled process-group leader identity could not be verified", + })), + ); + const scanFailures = tokenOwned.failures.filter(({ pid }) => + members.some((member) => member.pid === pid), + ); + if (failures.length > 0) unresolved.push(...failures); + if ( + failures.length === 0 && + scanFailures.length === 0 && + !unverifiableSampledGroups.has(processGroupId) + ) + signalableGroups.add(processGroupId); + } + if (ownership.sweepTokenOwnedProcesses !== true && unresolved.length > 0) + return cleanupValidationFailure(unresolved); return { - signalOrder: [ownership.processGroupId].concat( - [...processGroupIds] - .filter((processGroupId) => processGroupId !== ownership.processGroupId) - .sort((left, right) => left - right), - ), + signalOrder: [...signalableGroups] + .filter((groupId) => processGroupIds.has(groupId)) + .sort((left, right) => + left === ownership.processGroupId + ? -1 + : right === ownership.processGroupId + ? 1 + : left - right, + ), + tokenOwnedIdentities: tokenOwned.identities, + unresolved, }; }; @@ -320,6 +433,7 @@ const revalidateOwnedProcessGroup = async ( processGroupId: number, ownership: OwnedProcessGroup, host: ProcessOwnershipHost, + expectedIdentities: ReadonlyMap, ): Promise<{ readonly empty: boolean } | ProcessCleanupResult> => { let members: readonly ProcessTableEntry[]; try { @@ -337,6 +451,7 @@ const revalidateOwnedProcessGroup = async ( members, ownership.runId, host, + expectedIdentities, ); if (failures.length > 0) return cleanupValidationFailure(failures); if (processGroupId === ownership.processGroupId) { @@ -363,20 +478,102 @@ const revalidateOwnedProcessGroup = async ( const cleanupValidationFailure = ( failures: readonly ProcessOwnershipValidationFailure[], -): ProcessCleanupResult => ({ - cleaned: false, - reason: failures.some(({ reason }) => reason === "run-token-mismatch") - ? "process tree contains an unowned or PID-reused process" - : "process ownership could not be revalidated", - failures, -}); +): ProcessCleanupResult => { + const unreadable = failures.filter( + ({ reason }) => reason === "environment-unreadable", + ); + if (failures.some(({ reason }) => reason === "run-token-mismatch")) + return { + cleaned: false, + reason: "process tree contains an unowned or PID-reused process", + failures, + }; + if (failures.some(({ reason }) => reason === "process-identity-unavailable")) + return { + cleaned: false, + reason: "process identity could not be revalidated", + failures, + }; + if (unreadable.length > 0) { + const counts = new Map(); + for (const { diagnostic } of unreadable) { + const category = sanitizedTokenReadFailure(diagnostic); + counts.set(category, (counts.get(category) ?? 0) + 1); + } + const breakdown = [...counts] + .sort(([left], [right]) => left.localeCompare(right)) + .map(([category, count]) => `${category}=${String(count)}`) + .join(", "); + return { + cleaned: false, + reason: `process ownership token could not be read for ${String(unreadable.length)} live process(es): ${breakdown}`, + failures, + }; + } + return { + cleaned: false, + reason: "process ownership could not be revalidated", + failures, + }; +}; + +const sanitizedTokenReadFailure = (diagnostic: string | undefined): string => { + if (diagnostic === "environment_unavailable") return diagnostic; + const environmentErrno = /^(EACCES|EPERM|ENOENT|ESRCH):/u.exec( + diagnostic ?? "", + ); + if (environmentErrno?.[1] !== undefined) + return `environment_errno_${environmentErrno[1]}`; + if (diagnostic === "apple_vector_unavailable") return diagnostic; + if (diagnostic === "ambiguous_environment_boundary") return diagnostic; + if (diagnostic === "malformed_procargs") return diagnostic; + if (diagnostic === "process_identity_changed_during_token_read") + return diagnostic; + if (diagnostic === "process_not_in_snapshot") return diagnostic; + const processTableFailure = /^process_table_failed_(\d+)$/u.exec( + diagnostic ?? "", + ); + if (processTableFailure?.[1] !== undefined) + return `process_table_errno_${processTableFailure[1]}`; + const sysctlFailure = /^sysctl_failed_(\d+)$/u.exec(diagnostic ?? ""); + if (sysctlFailure?.[1] !== undefined) + return `sysctl_errno_${sysctlFailure[1]}`; + if (diagnostic === "reader_failure") return diagnostic; + if (diagnostic === "duplicate_run_token") return diagnostic; + if (diagnostic === "invalid_run_token_encoding") return diagnostic; + return "other_unavailable"; +}; const processOwnershipFailures = async ( members: readonly ProcessTableEntry[], runId: string, host: ProcessOwnershipHost, + expectedIdentities: ReadonlyMap = new Map(), ): Promise => { const failures: ProcessOwnershipValidationFailure[] = []; + if (expectedIdentities.size > 0 && host.processIdentities !== undefined) { + const expectedMembers = members.filter(({ pid }) => + expectedIdentities.has(pid), + ); + const observed = await host.processIdentities(expectedMembers); + for (const member of expectedMembers) { + const expected = expectedIdentities.get(member.pid); + const current = observed.get(member.pid); + if ( + expected === null || + current?.state !== "readable" || + current.identity !== expected + ) { + if (await processIsGone(host, member.pid)) continue; + failures.push({ + pid: member.pid, + reason: "process-identity-unavailable", + diagnostic: + "process identity changed or became unavailable before signal", + }); + } + } + } for (const member of members) { try { if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !== runId) @@ -400,25 +597,123 @@ const processOwnershipFailures = async ( }); } } + if ( + failures.length === 0 && + expectedIdentities.size > 0 && + host.processIdentities !== undefined + ) { + const expectedMembers = members.filter(({ pid }) => + expectedIdentities.has(pid), + ); + const observed = await host.processIdentities(expectedMembers); + for (const member of expectedMembers) { + const expected = expectedIdentities.get(member.pid); + const current = observed.get(member.pid); + if ( + expected !== null && + current?.state === "readable" && + current.identity === expected + ) + continue; + if (await processIsGone(host, member.pid)) continue; + failures.push({ + pid: member.pid, + reason: "process-identity-unavailable", + diagnostic: + "process identity changed or became unavailable during token validation", + }); + } + } return failures; }; interface TokenOwnedProcessScan { readonly owned: readonly ProcessTableEntry[]; readonly failures: readonly ProcessOwnershipValidationFailure[]; + readonly identities: ReadonlyMap; } const scanTokenOwnedProcesses = async ( processes: readonly ProcessTableEntry[], runId: string, host: ProcessOwnershipHost, + captureBaseline?: ProcessOwnershipBaseline, ): Promise => { - const owned: ProcessTableEntry[] = []; + let candidates = processes; + let candidateIdentities: ReadonlyMap = + new Map(); + if (host.processIdentities !== undefined) { + const current = await host.processIdentities(processes); + candidateIdentities = current; + if (captureBaseline !== undefined) { + const baseline = new Map( + captureBaseline.map(({ pid, identity }) => [pid, identity]), + ); + candidates = processes.filter((process) => { + const before = baseline.get(process.pid); + const now = current.get(process.pid); + return !( + before !== undefined && + before !== null && + now?.state === "readable" && + now.identity === before + ); + }); + } + } const failures: ProcessOwnershipValidationFailure[] = []; - for (const process of processes) { + if (captureBaseline !== undefined && host.processIdentities !== undefined) { + const stableCandidates: ProcessTableEntry[] = []; + for (const process of candidates) { + if (candidateIdentities.get(process.pid)?.state === "readable") { + stableCandidates.push(process); + continue; + } + if (!(await processIsGone(host, process.pid))) + failures.push({ + pid: process.pid, + reason: "process-identity-unavailable", + diagnostic: "process identity was unavailable during token scan", + }); + } + candidates = stableCandidates; + } + let bulkTokens: ReadonlyMap | undefined; + if (host.runTokens !== undefined) + bulkTokens = await host.runTokens(candidates); + const owned: ProcessTableEntry[] = []; + const ownedIdentities = new Map(); + for (const process of candidates) { + const bulkToken = bulkTokens?.get(process.pid); + if (bulkToken !== undefined) { + if (bulkToken.state === "readable") { + if (bulkToken.runId === runId) { + owned.push(process); + const identity = candidateIdentities.get(process.pid); + ownedIdentities.set( + process.pid, + identity?.state === "readable" ? identity.identity : null, + ); + } + continue; + } + if (!(await processIsGone(host, process.pid))) + failures.push({ + pid: process.pid, + reason: "environment-unreadable", + diagnostic: bulkToken.reason, + }); + continue; + } try { - if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId) + if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId) { owned.push(process); + const identity = candidateIdentities.get(process.pid); + ownedIdentities.set( + process.pid, + identity?.state === "readable" ? identity.identity : null, + ); + } } catch (cause: unknown) { try { const live = liveProcesses(await host.listProcesses()); @@ -438,5 +733,44 @@ const scanTokenOwnedProcesses = async ( }); } } - return { owned, failures }; + if (candidates.length > 0 && host.processIdentities !== undefined) { + const afterRead = await host.processIdentities(candidates); + const stillOwned: ProcessTableEntry[] = []; + const ownedPids = new Set(owned.map(({ pid }) => pid)); + for (const process of candidates) { + const before = candidateIdentities.get(process.pid); + const current = afterRead.get(process.pid); + if ( + before?.state === "readable" && + current?.state === "readable" && + current.identity === before.identity + ) { + if (ownedPids.has(process.pid)) stillOwned.push(process); + continue; + } + if (await processIsGone(host, process.pid)) continue; + failures.push({ + pid: process.pid, + reason: "process-identity-unavailable", + diagnostic: + "process identity changed or became unavailable during token validation", + }); + } + return { owned: stillOwned, failures, identities: ownedIdentities }; + } + return { owned, failures, identities: ownedIdentities }; +}; + +const processIsGone = async ( + host: ProcessOwnershipHost, + pid: number, +): Promise => { + try { + return !liveProcesses(await host.listProcesses()).some( + (process) => process.pid === pid, + ); + } catch (cause: unknown) { + void cause; + return false; + } }; diff --git a/src/process/ProcessOwnershipObservation.ts b/src/process/ProcessOwnershipObservation.ts index ee835dcb9..c33663f02 100644 --- a/src/process/ProcessOwnershipObservation.ts +++ b/src/process/ProcessOwnershipObservation.ts @@ -3,11 +3,14 @@ import { readFile } from "node:fs/promises"; import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js"; import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js"; import { execFileOutput } from "./ExecFileOutput.js"; +import { createDarwinProcessRunTokenReader } from "./DarwinProcessRunTokenReader.js"; import type { OwnedProcessGroup, ProcessGroupObservation, ProcessLineageObservation, ProcessOwnershipHost, + ProcessIdentityObservation, + ProcessOwnershipBaseline, ProcessTableEntry, } from "./ProcessOwnership.js"; @@ -144,18 +147,53 @@ export const parseProcessEnvironment = ( }), ); +/** Read Linux stat field 22, tolerating spaces and closing parentheses in comm. */ +export const parseLinuxProcessStartTime = ( + value: string, +): string | undefined => { + const commandEnd = value.lastIndexOf(")"); + if (commandEnd < 0) return undefined; + const fields = value + .slice(commandEnd + 1) + .trim() + .split(/\s+/u); + const startTime = fields[19]; + return startTime !== undefined && /^\d+$/u.test(startTime) + ? startTime + : undefined; +}; + +const isExpectedAbort = ( + cause: unknown, + signal: AbortSignal | undefined, +): boolean => + signal?.aborted === true && + (cause === signal.reason || + (cause instanceof Error && cause.name === "AbortError")); + /** Create the operating-system process inspector for an explicit host context. */ export const createSystemProcessOwnershipHost = ( platform: NodeJS.Platform = process.platform, hostEnvironment: NodeJS.ProcessEnv = process.env, -): ProcessOwnershipHost => ({ - platform, - async listProcesses() { + options: { + /** Override the compiler executable used by the Darwin native reader. */ + readonly darwinXcrun?: string; + } = {}, +): ProcessOwnershipHost => { + const darwinTokens = + platform === "darwin" + ? createDarwinProcessRunTokenReader( + options.darwinXcrun === undefined + ? {} + : { xcrun: options.darwinXcrun }, + ) + : undefined; + const listProcesses = async (signal?: AbortSignal) => { if (platform === "win32") return []; const { stdout } = await execFileOutput( "ps", ["-axo", "pid=,ppid=,pgid=,stat=,command="], - { env: hostEnvironment }, + { env: hostEnvironment, ...(signal === undefined ? {} : { signal }) }, ); return stdout .split("\n") @@ -168,35 +206,175 @@ export const createSystemProcessOwnershipHost = ( state: match[4] ?? "", command: match[5] ?? "", })); - }, - async environment(pid) { + }; + const processIdentities: NonNullable< + ProcessOwnershipHost["processIdentities"] + > = async (processes, signal) => { + if (processes.length === 0) return new Map(); if (platform === "linux") - return parseProcessEnvironment( - await readFile(`/proc/${pid}/environ`, "utf8"), + return new Map( + await Promise.all( + processes.map(async ({ pid }) => { + try { + const stat = await readFile(`/proc/${String(pid)}/stat`, { + encoding: "utf8", + ...(signal === undefined ? {} : { signal }), + }); + const identity = parseLinuxProcessStartTime(stat); + return [ + pid, + identity === undefined + ? ({ + state: "unavailable", + reason: "malformed_proc_stat", + } as const) + : ({ + state: "readable", + identity: `linux-starttime:${identity}`, + } as const), + ] as const; + } catch (cause: unknown) { + if (isExpectedAbort(cause, signal)) throw cause; + return [ + pid, + { state: "unavailable", reason: errorMessage(cause) } as const, + ] as const; + } + }), + ), + ); + if (darwinTokens === undefined) return new Map(); + try { + return await darwinTokens.identities(processes, signal); + } catch (cause: unknown) { + if (isExpectedAbort(cause, signal)) throw cause; + const reason = errorMessage(cause); + return new Map( + processes.map(({ pid }) => [ + pid, + { state: "unavailable", reason } as const, + ]), ); - const { stdout } = await execFileOutput("ps", ["eww", "-p", String(pid)], { - env: hostEnvironment, - }); - const observedEnvironment: Record = {}; - for (const match of stdout.matchAll( - /(?:^|\s)([A-Za-z_][A-Za-z0-9_]*)=([^\s]*)/gu, - )) { - const name = match[1]; - if (name !== undefined) observedEnvironment[name] = match[2] ?? ""; } - return observedEnvironment; - }, - signalGroup(processGroupId, signal) { - process.kill(-processGroupId, signal); - }, -}); + }; + const captureBaseline = async ( + signal?: AbortSignal, + ): Promise => { + if (platform !== "linux" && darwinTokens === undefined) return []; + const processes = liveProcesses(await listProcesses(signal)); + let identities = await processIdentities(processes, signal); + const unreadable = processes.filter( + ({ pid }) => identities.get(pid)?.state !== "readable", + ); + if (unreadable.length > 0) { + const stillLive = liveProcesses(await listProcesses(signal)).filter( + ({ pid }) => unreadable.some((entry) => entry.pid === pid), + ); + const retried = await processIdentities(stillLive, signal); + identities = new Map([...identities, ...retried]); + const unresolved = stillLive.filter( + ({ pid }) => identities.get(pid)?.state !== "readable", + ); + const liveAfterRetry = liveProcesses(await listProcesses(signal)); + const stillUnresolved = unresolved.filter(({ pid }) => + liveAfterRetry.some((entry) => entry.pid === pid), + ); + if (stillUnresolved.length > 0) + throw new Error( + `process identity snapshot is unavailable for ${String(stillUnresolved.length)} live processes`, + ); + } + return processes.map(({ pid }) => { + const observation = identities.get(pid); + return { + pid, + identity: + observation?.state === "readable" ? observation.identity : null, + }; + }); + }; + return { + platform, + prepare: async (signal) => { + await darwinTokens?.prepare(signal); + }, + listProcesses, + async environment(pid) { + if (platform === "linux") + return parseProcessEnvironment( + await readFile(`/proc/${pid}/environ`, "utf8"), + ); + if (platform === "darwin") { + const process = (await listProcesses()).find( + (entry) => entry.pid === pid, + ); + if (process === undefined) + throw new Error(`process ${String(pid)} is not live`); + const observation = (await darwinTokens?.read([process]))?.get(pid); + if (observation === undefined || observation.state === "unavailable") + throw new Error( + observation?.reason ?? "process run token could not be read", + ); + return observation.runId === undefined + ? {} + : { REA_PROCESS_RUN_ID: observation.runId }; + } + const { stdout } = await execFileOutput( + "ps", + ["eww", "-p", String(pid)], + { + env: hostEnvironment, + }, + ); + const observedEnvironment: Record = {}; + for (const match of stdout.matchAll( + /(?:^|\s)([A-Za-z_][A-Za-z0-9_]*)=([^\s]*)/gu, + )) { + const name = match[1]; + if (name !== undefined) observedEnvironment[name] = match[2] ?? ""; + } + return observedEnvironment; + }, + async runTokens(processes) { + if (platform !== "darwin" || processes.length === 0) return new Map(); + try { + return (await darwinTokens?.read(processes)) ?? new Map(); + } catch (cause: unknown) { + void cause; + return new Map( + processes.map(({ pid }) => [ + pid, + { state: "unavailable", reason: "reader_failure" } as const, + ]), + ); + } + }, + ...(platform !== "linux" && darwinTokens === undefined + ? {} + : { + processIdentities, + captureBaseline, + ...(darwinTokens === undefined ? {} : { close: darwinTokens.close }), + }), + signalGroup(processGroupId, signal) { + process.kill(-processGroupId, signal); + }, + }; +}; -const systemHost = createSystemProcessOwnershipHost(); +export const systemProcessOwnershipHost = createSystemProcessOwnershipHost(); + +/** Prepare native token inspection before REA launches a captured child. */ +export const prepareProcessOwnershipInspection = async ( + signal?: AbortSignal, +): Promise => { + await systemProcessOwnershipHost.prepare?.(signal); +}; /** Observe one group without signaling it, failing closed on identity doubt. */ export const observeOwnedProcessGroup = async ( ownership: OwnedProcessGroup, - host: ProcessOwnershipHost = systemHost, + host: ProcessOwnershipHost = systemProcessOwnershipHost, ): Promise => observeOwnedProcessGroupWithHost(ownership, host); @@ -209,7 +387,7 @@ export const observeOwnedProcessGroup = async ( */ export const observeOwnedProcessLineage = async ( ownership: OwnedProcessGroup, - host: ProcessOwnershipHost = systemHost, + host: ProcessOwnershipHost = systemProcessOwnershipHost, ): Promise => observeOwnedProcessLineageWithHost(ownership, host); diff --git a/src/process/ProviderProcess.ts b/src/process/ProviderProcess.ts index d41432d1b..ef5ff8262 100644 --- a/src/process/ProviderProcess.ts +++ b/src/process/ProviderProcess.ts @@ -8,6 +8,7 @@ import type { OwnedProcessGroup, ProcessCleanupResult, } from "./ProcessOwnership.js"; +import { prepareProcessOwnershipInspection } from "./ProcessOwnershipObservation.js"; const DEFAULT_TERMINATION_GRACE_MS = 250; const DEFAULT_KILL_GRACE_MS = 1_000; @@ -29,6 +30,8 @@ export interface OwnedProviderProcessSpawnOptions { readonly hostEnvironment?: NodeJS.ProcessEnv; /** Opt into a writable protocol stream; other providers retain ignored stdin. */ readonly stdin?: "pipe"; + /** Cancels native ownership preparation before provider process creation. */ + readonly signal?: AbortSignal; } /** Spawned process paired with the identity proof required for group cleanup. */ @@ -69,6 +72,8 @@ export type ProviderProcessLaunch = /** Detached diagnostics captured for one supervised provider process. */ export interface ProviderProcessSnapshot { + /** True only when a configured diagnostic budget excluded output bytes. */ + readonly diagnosticTruncated?: boolean; readonly stdout: { readonly text: string; readonly bytes: number; @@ -88,6 +93,7 @@ export type ProviderProcessDiagnostic = readonly stream: "stdout" | "stderr"; readonly bytes: number; readonly totalBytes: number; + readonly truncated?: boolean; } | { readonly type: "exit"; @@ -101,6 +107,8 @@ export type ProviderProcessDiagnostic = export interface ProviderProcessSupervisorOptions { /** Retain stdout for diagnostics; disable when it carries a parsed protocol. Defaults to true. */ readonly captureStdout?: boolean; + /** Aggregate retained stdout/stderr byte budget; overflow is drained and reported, not retained. */ + readonly maxDiagnosticBytes?: number; readonly onDiagnostic?: (event: ProviderProcessDiagnostic) => void; } @@ -132,6 +140,9 @@ export type ProviderProcessStopResult = export const spawnOwnedProviderProcess = async ( options: OwnedProviderProcessSpawnOptions, ): Promise => { + options.signal?.throwIfAborted(); + await prepareProcessOwnershipInspection(options.signal); + options.signal?.throwIfAborted(); const platform = options.platform ?? process.platform; const hostEnvironment = options.hostEnvironment ?? process.env; const environment = { @@ -203,6 +214,9 @@ export class ProviderProcessSupervisor { }>(); readonly #outputClose = deferred(); #outputClosed = false; + #stdoutObservedBytes = 0; + #stderrObservedBytes = 0; + #diagnosticTruncated = false; readonly #onStdout = (chunk: Buffer | string): void => { this.#capture("stdout", this.#stdout, chunk); }; @@ -247,9 +261,10 @@ export class ProviderProcessSupervisor { this.#recordExit(launch.process.exitCode, launch.process.signalCode); } - /** Latest complete output and exit observation. */ + /** Latest retained output and exit observation, with explicit configured truncation. */ snapshot(): ProviderProcessSnapshot { return { + ...(this.#diagnosticTruncated ? { diagnosticTruncated: true } : {}), stdout: this.#stdout.snapshot(), stderr: this.#stderr.snapshot(), exitCode: this.#exitObservation?.code, @@ -261,6 +276,9 @@ export class ProviderProcessSupervisor { resetOutput(): void { this.#stdout.reset(); this.#stderr.reset(); + this.#stdoutObservedBytes = 0; + this.#stderrObservedBytes = 0; + this.#diagnosticTruncated = false; } /** Wait for process exit up to a caller-owned bounded interval. */ @@ -382,12 +400,29 @@ export class ProviderProcessSupervisor { chunk: Buffer | string, ): void { const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); - capture.append(bytes); + if (stream === "stdout") this.#stdoutObservedBytes += bytes.byteLength; + else this.#stderrObservedBytes += bytes.byteLength; + const remaining = + this.#options.maxDiagnosticBytes === undefined + ? bytes.byteLength + : Math.max( + 0, + this.#options.maxDiagnosticBytes - + this.#stdout.bytes - + this.#stderr.bytes, + ); + const retained = Math.min(bytes.byteLength, remaining); + if (retained < bytes.byteLength) this.#diagnosticTruncated = true; + if (retained > 0) capture.append(bytes.subarray(0, retained)); this.#options.onDiagnostic?.({ type: "output", stream, bytes: bytes.byteLength, - totalBytes: capture.bytes, + totalBytes: + stream === "stdout" + ? this.#stdoutObservedBytes + : this.#stderrObservedBytes, + ...(retained < bytes.byteLength ? { truncated: true } : {}), }); } diff --git a/src/process/capture/FilesystemSnapshot.test.ts b/src/process/capture/FilesystemSnapshot.test.ts new file mode 100644 index 000000000..b7e141018 --- /dev/null +++ b/src/process/capture/FilesystemSnapshot.test.ts @@ -0,0 +1,127 @@ +import { fstatSync } from "node:fs"; +import { + mkdtemp, + lstat, + open, + readdir, + rm, + utimes, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { expect, it } from "vitest"; + +import { hashFile } from "./FilesystemSnapshot.js"; + +const countOpenDescriptorsFor = async (path: string): Promise => { + const directory = process.platform === "linux" ? "/proc/self/fd" : "/dev/fd"; + const expected = await lstat(path); + const descriptors = await readdir(directory); + const matches = await Promise.all( + descriptors.map(async (descriptor) => { + if (!/^\d+$/u.test(descriptor)) return false; + try { + const stats = fstatSync(Number(descriptor)); + return stats.dev === expected.dev && stats.ino === expected.ino; + } catch (cause: unknown) { + if (cause instanceof Error && "code" in cause && cause.code === "EBADF") + return false; + throw cause; + } + }), + ); + return matches.filter(Boolean).length; +}; + +it("does not hash a replacement file using the earlier path metadata", async () => { + const directory = await mkdtemp(join(tmpdir(), "rea-snapshot-identity-")); + const path = join(directory, "observed-file"); + try { + await writeFile(path, "captured file\n"); + const expected = await lstat(path); + await rm(path); + await writeFile(path, "replacement with a different identity and size\n"); + + await expect(hashFile(path, expected, 1_000)).resolves.toBeNull(); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); + +it("rejects a same-size rewrite even when its modification time is restored", async () => { + const directory = await mkdtemp(join(tmpdir(), "rea-snapshot-ctime-")); + const path = join(directory, "observed-file"); + try { + const fixedMtime = new Date("2020-01-02T03:04:05.000Z"); + await writeFile(path, "before\n"); + await utimes(path, fixedMtime, fixedMtime); + const expected = await lstat(path); + await expect + .poll(async () => { + await writeFile(path, "after!\n"); + await utimes(path, fixedMtime, fixedMtime); + return (await lstat(path)).ctimeMs; + }) + .not.toBe(expected.ctimeMs); + const rewritten = await lstat(path); + + expect(rewritten).toMatchObject({ + dev: expected.dev, + ino: expected.ino, + mode: expected.mode, + size: expected.size, + mtimeMs: expected.mtimeMs, + }); + expect(rewritten.ctimeMs).not.toBe(expected.ctimeMs); + + await expect(hashFile(path, expected, 1_000)).resolves.toBeNull(); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); + +it("rejects cancellation for an empty file before returning its digest", async () => { + const directory = await mkdtemp(join(tmpdir(), "rea-snapshot-empty-cancel-")); + const path = join(directory, "observed-file"); + try { + await writeFile(path, ""); + const expected = await lstat(path); + const controller = new AbortController(); + controller.abort(); + + await expect( + hashFile(path, expected, 1_000, controller.signal), + ).rejects.toMatchObject({ name: "AbortError" }); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); + +it.skipIf(process.platform === "win32")( + "closes the opened file when cancellation arrives during open", + async () => { + const directory = await mkdtemp(join(tmpdir(), "rea-snapshot-cancel-")); + const path = join(directory, "observed-file"); + try { + await writeFile(path, "captured file\n"); + const expected = await lstat(path); + const controller = new AbortController(); + const held = await open(path, "r"); + expect(await countOpenDescriptorsFor(path)).toBe(1); + await held.close(); + expect(await countOpenDescriptorsFor(path)).toBe(0); + const hashing = hashFile(path, expected, 1_000, controller.signal); + // hashFile reaches `open` synchronously before its first suspension, so + // this abort is observed after the descriptor is acquired and is handled + // by the `finally` close path. + controller.abort(); + + await expect(hashing).rejects.toMatchObject({ name: "AbortError" }); + expect(await countOpenDescriptorsFor(path)).toBe(0); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }, +); diff --git a/src/application/FilesystemSnapshot.ts b/src/process/capture/FilesystemSnapshot.ts similarity index 81% rename from src/application/FilesystemSnapshot.ts rename to src/process/capture/FilesystemSnapshot.ts index 246497d4a..aaf15fda8 100644 --- a/src/application/FilesystemSnapshot.ts +++ b/src/process/capture/FilesystemSnapshot.ts @@ -2,7 +2,10 @@ import { createHash } from "node:crypto"; import { constants as fsConstants } from "node:fs"; import { lstat, open, readdir, readlink } from "node:fs/promises"; import { dirname, join, relative, resolve } from "node:path"; -import type { FileState, ProcessScenario } from "../domain/processCapture.js"; +import type { + FileState, + ProcessScenario, +} from "../../domain/process/processCapture.js"; import type { Stats } from "node:fs"; export interface SnapshotResult { @@ -19,6 +22,16 @@ const hasSameIdentity = ( before.ino === after.ino && (expected === "directory" ? after.isDirectory() : after.isSymbolicLink()); +const hasSameFileState = (before: Stats, after: Stats): boolean => + before.dev === after.dev && + before.ino === after.ino && + before.isFile() && + after.isFile() && + before.mode === after.mode && + before.size === after.size && + before.mtimeMs === after.mtimeMs && + before.ctimeMs === after.ctimeMs; + const lstatIfPresent = async (path: string): Promise => { try { return await lstat(path); @@ -29,18 +42,26 @@ const lstatIfPresent = async (path: string): Promise => { } }; -const hashFile = async ( +/** Hash a file only when its opened descriptor still matches the captured path state. */ +export const hashFile = async ( path: string, + expected: Stats, maxBytes: number, signal?: AbortSignal, ): Promise => { + signal?.throwIfAborted(); const handle = await open( path, - fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0), + fsConstants.O_RDONLY | + (fsConstants.O_NOFOLLOW ?? 0) | + (fsConstants.O_NONBLOCK ?? 0), ); try { + signal?.throwIfAborted(); const stats = await handle.stat(); - if (!stats.isFile() || stats.size > maxBytes) return null; + signal?.throwIfAborted(); + if (!hasSameFileState(expected, stats) || stats.size > maxBytes) + return null; const hash = createHash("sha256"); const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes)); let position = 0; @@ -57,8 +78,8 @@ const hashFile = async ( position += bytesRead; } const after = await handle.stat(); - if (after.size !== stats.size || after.mtimeMs !== stats.mtimeMs) - return null; + signal?.throwIfAborted(); + if (!hasSameFileState(stats, after)) return null; return hash.digest("hex"); } finally { await handle.close(); @@ -110,7 +131,7 @@ export const snapshotRoots = async ( if (stats.isFile()) { const sha256 = remainingBytes >= stats.size - ? await hashFile(path, remainingBytes, signal) + ? await hashFile(path, stats, remainingBytes, signal) : null; remainingBytes -= sha256 === null ? 0 : stats.size; if (sha256 === null) truncated = true; diff --git a/src/application/ProcessCaptureCapability.ts b/src/process/capture/ProcessCaptureCapability.ts similarity index 86% rename from src/application/ProcessCaptureCapability.ts rename to src/process/capture/ProcessCaptureCapability.ts index e7ec0ef3a..7d0de7625 100644 --- a/src/application/ProcessCaptureCapability.ts +++ b/src/process/capture/ProcessCaptureCapability.ts @@ -1,4 +1,5 @@ import { tmpdir } from "node:os"; +import { prepareProcessOwnershipInspection } from "../ProcessOwnershipObservation.js"; export type ProcessCaptureCapability = | { readonly available: true; readonly backend: "node-pty" } @@ -20,6 +21,7 @@ export const processCaptureOwnershipUnavailableReason = ( export interface ProcessCaptureProbeOptions { readonly platform?: NodeJS.Platform; readonly loadPty?: () => Promise; + readonly prepareOwnershipInspector?: () => Promise; } /** Probe the native PTY seam and explain loader failures at their source. */ @@ -56,6 +58,22 @@ export const probeProcessCaptureCapability = async ( backend: "node-pty", reason: ownershipReason, }; + if (platform === "darwin") { + try { + await ( + options.prepareOwnershipInspector ?? prepareProcessOwnershipInspection + )(); + } catch (cause: unknown) { + return { + available: false, + backend: "node-pty", + reason: + cause instanceof Error + ? cause.message + : "macOS process ownership inspection could not be prepared", + }; + } + } try { const { spawn } = await ( options.loadPty ?? (() => import("@lydell/node-pty")) diff --git a/src/application/ProcessCaptureEnvironment.ts b/src/process/capture/ProcessCaptureEnvironment.ts similarity index 90% rename from src/application/ProcessCaptureEnvironment.ts rename to src/process/capture/ProcessCaptureEnvironment.ts index 1414349cd..af4d592f6 100644 --- a/src/application/ProcessCaptureEnvironment.ts +++ b/src/process/capture/ProcessCaptureEnvironment.ts @@ -1,4 +1,4 @@ -import type { ProcessScenario } from "../domain/processCapture.js"; +import type { ProcessScenario } from "../../domain/process/processCapture.js"; interface ProcessCaptureEnvironmentOptions { readonly scenario: ProcessScenario; diff --git a/src/application/ProcessCaptureError.test.ts b/src/process/capture/ProcessCaptureError.test.ts similarity index 92% rename from src/application/ProcessCaptureError.test.ts rename to src/process/capture/ProcessCaptureError.test.ts index 3dedc4260..d554d8ee8 100644 --- a/src/application/ProcessCaptureError.test.ts +++ b/src/process/capture/ProcessCaptureError.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; import { ProcessCaptureError, processCaptureCancelled, diff --git a/src/application/ProcessCaptureError.ts b/src/process/capture/ProcessCaptureError.ts similarity index 56% rename from src/application/ProcessCaptureError.ts rename to src/process/capture/ProcessCaptureError.ts index da57679ab..4f2422559 100644 --- a/src/application/ProcessCaptureError.ts +++ b/src/process/capture/ProcessCaptureError.ts @@ -1,10 +1,17 @@ -import { AnalysisError } from "../domain/analysisErrorBase.js"; +import { AnalysisError } from "../../domain/analysisErrorBase.js"; +import type { + PartialProcessCaptureObservation, + ProcessCaptureCleanupReport, +} from "../../domain/process/processCapture.js"; interface ProcessCaptureErrorOptions extends ErrorOptions { readonly userMessage?: string; readonly userCategory?: "cancelled"; readonly reason?: "capture_failed" | "cleanup_incomplete" | "cancelled"; readonly cleanupResources?: readonly string[]; + readonly executionFailure?: string; + readonly partialObservation?: PartialProcessCaptureObservation; + readonly cleanupReport?: ProcessCaptureCleanupReport; } /** Typed application failure produced by controlled process capture. */ @@ -16,6 +23,11 @@ export class ProcessCaptureError extends AnalysisError { readonly reason: NonNullable; override readonly cleanupIncomplete: boolean; override readonly cleanupResources: readonly string[]; + override readonly executionFailure: string | undefined; + override readonly partialObservation: + | PartialProcessCaptureObservation + | undefined; + override readonly cleanupReport: ProcessCaptureCleanupReport | undefined; constructor(message: string, options?: ProcessCaptureErrorOptions) { super(message, options); @@ -26,9 +38,22 @@ export class ProcessCaptureError extends AnalysisError { (options?.userCategory === "cancelled" ? "cancelled" : "capture_failed"); this.cleanupIncomplete = this.reason === "cleanup_incomplete"; this.cleanupResources = options?.cleanupResources ?? []; + this.executionFailure = options?.executionFailure; + this.partialObservation = options?.partialObservation; + this.cleanupReport = options?.cleanupReport; } } +/** Return one non-recursive, domain-neutral summary of an execution failure. */ +export const describeProcessCaptureExecutionFailure = ( + failure: unknown, +): string | undefined => { + if (failure instanceof Error) return failure.message || failure.name; + if (typeof failure === "string") return failure; + if (failure === undefined) return undefined; + return "Process capture failed with a non-error rejection."; +}; + /** Caller-visible cancellation without exposing capture implementation state. */ export const processCaptureCancelled = (): ProcessCaptureError => new ProcessCaptureError("process capture was cancelled", { diff --git a/src/application/ProcessCaptureJournal.test.ts b/src/process/capture/ProcessCaptureJournal.test.ts similarity index 100% rename from src/application/ProcessCaptureJournal.test.ts rename to src/process/capture/ProcessCaptureJournal.test.ts diff --git a/src/application/ProcessCaptureJournal.ts b/src/process/capture/ProcessCaptureJournal.ts similarity index 98% rename from src/application/ProcessCaptureJournal.ts rename to src/process/capture/ProcessCaptureJournal.ts index a40f11ac3..027d193c4 100644 --- a/src/application/ProcessCaptureJournal.ts +++ b/src/process/capture/ProcessCaptureJournal.ts @@ -5,7 +5,7 @@ import type { ProcessCaptureEventJournalEntry, ProcessScenario, RecordProcessCaptureEvent, -} from "../domain/processCapture.js"; +} from "../../domain/process/processCapture.js"; import { normalizeProcessElapsedTime, normalizeProcessText, diff --git a/src/process/capture/ProcessCaptureLifecycle.ts b/src/process/capture/ProcessCaptureLifecycle.ts new file mode 100644 index 000000000..5cd75bf1a --- /dev/null +++ b/src/process/capture/ProcessCaptureLifecycle.ts @@ -0,0 +1,964 @@ +import { createHash, randomUUID } from "node:crypto"; +import { constants, statSync, type BigIntStats } from "node:fs"; +import { open, mkdtemp, rm, stat } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { IPty } from "@lydell/node-pty"; + +import type { + FilesystemCheckpoint, + InteractionEvent, + ProcessCapture, + UnverifiedProcessCapture, + ProcessSample, + ProcessSettlement, + ProcessCaptureCleanupReport, + PartialProcessCaptureObservation, + IncompleteProcessCaptureObservations, + PartialFilesystemSnapshot, + PartialProcessObservationField, + ProcessScenario, + ProcessCaptureEventJournalEntry, + RecordProcessCaptureEvent, + TerminalFrame, +} from "../../domain/process/processCapture.js"; +import { + digestProcessCommitment, + parseProcessCapture, + partialProcessCaptureObservationSchema, + processComparisonContract, + processScenarioCommitment, +} from "../../domain/process/processCapture.js"; +import { PRODUCT_IDENTITY } from "../../identity.js"; +import type { SnapshotResult } from "./FilesystemSnapshot.js"; +import { snapshotRoots } from "./FilesystemSnapshot.js"; +import { classifyFilesystemEffects } from "./ProcessFilesystemEffects.js"; +import { + cleanupOwnedProcessGroup, + verifyNoTokenOwnedProcesses, + type ProcessCleanupResult, + type ProcessOwnershipBaseline, +} from "../ProcessOwnership.js"; +import { + observeOwnedProcessGroup, + prepareProcessOwnershipInspection, + systemProcessOwnershipHost, +} from "../ProcessOwnershipObservation.js"; +import { + describeProcessCaptureExecutionFailure, + ProcessCaptureError, +} from "./ProcessCaptureError.js"; +import { assertNotCancelled } from "./ProcessScenarioRuntimeValidation.js"; +import { + normalizeProcessElapsedTime, + normalizeProcessSamples, + normalizeProcessText, +} from "./ProcessNormalization.js"; +import { PROCESS_PROVIDER } from "../../domain/process/processEvidenceProvider.js"; +import { TerminalRenderer } from "./TerminalRenderer.js"; +import { scheduleProcessInterval, type ProcessTimer } from "./ProcessTimer.js"; + +interface TerminalExitOptions { + readonly terminal: IPty; + readonly scenario: ProcessScenario; + readonly started: number; + readonly lastOutput: () => number; + readonly signal: AbortSignal | undefined; + readonly timers: Set; + readonly interactions: InteractionEvent[]; + readonly dispatchedEventIndexes: ReadonlySet; + readonly recordEvent: RecordProcessCaptureEvent; +} + +interface CaptureResultOptions { + readonly frames: readonly TerminalFrame[]; + readonly exit: { + readonly exitCode: number; + readonly signal?: number; + readonly reason: "exited" | "timeout" | "idle_timeout"; + }; + readonly samples: readonly ProcessSample[]; + readonly before: SnapshotResult; + readonly after: SnapshotResult; + readonly truncated: boolean; + readonly scenario: ProcessScenario; + readonly rootPid: number; + readonly samplingPartial: boolean; + readonly renderedFrames: UnverifiedProcessCapture["rendered_frames"]; + readonly interactions: readonly InteractionEvent[]; + readonly checkpoints: readonly FilesystemCheckpoint[]; + readonly settlement: ObservedProcessSettlement; + readonly manifest: UnverifiedProcessCapture["manifest"]; + readonly eventJournal: readonly ProcessCaptureEventJournalEntry[]; +} + +/** Settlement observation before process-resource cleanup has completed. */ +export type ObservedProcessSettlement = + | Pick< + Extract, + "state" | "elapsed_ms" + > + | Pick< + Exclude, + "state" | "elapsed_ms" + >; + +/** Capture observations before owned-resource cleanup has been verified. */ +export type PendingProcessCapture = Omit< + UnverifiedProcessCapture, + "cleanup" | "settlement" +> & { + readonly settlement: ObservedProcessSettlement; +}; + +/** Mutable collection of facts available if completion stops before a capture exists. */ +export interface ProcessCaptureObservationBuffer { + target_pid: PartialProcessObservationField; + frames: PartialProcessObservationField; + rendered_frames: PartialProcessObservationField< + UnverifiedProcessCapture["rendered_frames"] + >; + interaction_events: PartialProcessObservationField< + UnverifiedProcessCapture["interaction_events"] + >; + exit: IncompleteProcessCaptureObservations["exit"]; + settlement: IncompleteProcessCaptureObservations["settlement"]; + process_samples: PartialProcessObservationField; + filesystem_snapshots: { + before: PartialProcessObservationField; + after: PartialProcessObservationField; + }; + event_journal: PartialProcessObservationField< + readonly ProcessCaptureEventJournalEntry[] + >; + manifest: IncompleteProcessCaptureObservations["manifest"]; +} + +/** Seed explicit observation availability before capture completion begins. */ +export const createProcessCaptureObservationBuffer = (options: { + readonly frames: readonly TerminalFrame[]; + readonly interactions: readonly InteractionEvent[]; + readonly samples: readonly ProcessSample[]; + readonly eventJournal: readonly ProcessCaptureEventJournalEntry[]; + readonly before: SnapshotResult; +}): ProcessCaptureObservationBuffer => ({ + target_pid: { + state: "unavailable", + reason: "No PTY process was spawned before the run failed.", + }, + frames: { state: "available", value: options.frames }, + rendered_frames: { + state: "unavailable", + reason: + "Rendered terminal frames were not collected before capture completion finished.", + }, + interaction_events: { state: "available", value: options.interactions }, + exit: { + state: "unavailable", + reason: "Terminal exit was not observed before the run failed.", + }, + settlement: { + state: "unavailable", + reason: + "Process settlement was not observed before capture completion finished.", + }, + process_samples: { state: "available", value: options.samples }, + filesystem_snapshots: { + before: { + state: "available", + value: { + files: options.before.files, + truncated: options.before.truncated, + }, + }, + after: { + state: "unavailable", + reason: + "Final filesystem snapshot was not collected before capture completion finished.", + }, + }, + event_journal: { state: "available", value: options.eventJournal }, + manifest: { + state: "unavailable", + reason: + "Capture manifest was not produced before capture completion finished.", + }, +}); + +const normalizePartialProcessObservations = ( + observations: IncompleteProcessCaptureObservations, + context: { readonly scenario: ProcessScenario; readonly rootPid?: number }, +): IncompleteProcessCaptureObservations => { + const targetPid = observations.target_pid; + const target_pid = + targetPid.state === "available" && context.rootPid !== undefined + ? { + state: "available" as const, + value: context.scenario.normalization.pids ? 1 : context.rootPid, + } + : targetPid; + const samples = observations.process_samples; + const process_samples = + samples.state === "available" && context.rootPid !== undefined + ? { + state: "available" as const, + value: normalizeProcessSamples( + samples.value, + context.scenario, + context.rootPid, + ), + } + : samples; + return { ...observations, target_pid, process_samples }; +}; + +/** Wait for trailing PTY callbacks before the capture becomes immutable. */ +export const settleProcessCaptureJournal = async ( + journal: readonly ProcessCaptureEventJournalEntry[], + quietMs = 25, + maxWaitMs = 500, +): Promise => { + const deadline = Date.now() + maxWaitMs; + let observed = journal.length; + let lastChangeAt = Date.now(); + while (Date.now() < deadline) { + await new Promise((resolve) => setTimeout(resolve, 4)); + if (journal.length !== observed) { + observed = journal.length; + lastChangeAt = Date.now(); + } else if (Date.now() - lastChangeAt >= quietMs) return; + } +}; + +export const buildCaptureResult = ( + options: CaptureResultOptions, +): PendingProcessCapture => { + const hasSensitiveScriptedInput = options.scenario.events.some( + (event) => event.type === "input" && event.sensitive, + ); + const sensitiveInputUnknown = + "Sensitive scripted input values are redacted from process capture Evidence."; + const filesystemObservationUnknown = + "No filesystem observation paths were selected; filesystem effects remain unknown."; + const hasFilesystemObservations = + options.scenario.filesystem_observation_paths.length > 0; + return { + manifest: options.manifest, + normalization: options.scenario.normalization, + frames: options.frames, + rendered_frames: options.renderedFrames, + interaction_events: options.interactions, + exit: { + code: + options.exit.reason === "exited" && options.exit.exitCode >= 0 + ? options.exit.exitCode + : null, + signal: options.exit.signal ?? null, + reason: options.exit.reason, + }, + settlement: options.settlement, + process_samples: normalizeProcessSamples( + options.samples, + options.scenario, + options.rootPid, + ), + filesystem_checkpoints: options.checkpoints, + event_journal: options.eventJournal, + files_before: options.before.files, + files_after: options.after.files, + filesystem_effects: classifyFilesystemEffects( + options.before.files, + options.after.files, + ), + truncated: options.truncated, + limitations: [ + "The executable digest is a prelaunch file sample; matching path metadata immediately after spawn does not prove an atomic operating-system image binding.", + "Process trees are sampled and may omit short-lived descendants.", + ...(options.samplingPartial + ? ["Process-tree sampling ended with an incomplete observation."] + : []), + "Filesystem observations are before/after snapshots, not syscall traces.", + "Inherited host environment variables are not recorded and may affect results.", + ...(!hasFilesystemObservations ? [filesystemObservationUnknown] : []), + ...(hasSensitiveScriptedInput ? [sensitiveInputUnknown] : []), + ], + residual_unknowns: [ + { + scope: "process", + reason: + "Process trees are sampled and may omit short-lived descendants.", + }, + { + scope: "process", + reason: + "The executable digest is a prelaunch file sample; operating-system image binding is not atomically observed.", + }, + { + scope: "environment", + reason: "Inherited host environment variables are not recorded.", + }, + { + scope: "network", + reason: "Network activity is not observed by this capture.", + }, + ...(!hasFilesystemObservations + ? [ + { + scope: "filesystem" as const, + reason: filesystemObservationUnknown, + }, + ] + : []), + ...(hasSensitiveScriptedInput + ? [{ scope: "interaction" as const, reason: sensitiveInputUnknown }] + : []), + ], + }; +}; + +interface ExecutableFileIdentity { + readonly dev: bigint; + readonly ino: bigint; + readonly mode: bigint; + readonly size: bigint; + readonly mtimeNs: bigint; + readonly ctimeNs: bigint; +} + +export interface SelectedExecutableObservation { + readonly sha256: string | null; + readonly identity: ExecutableFileIdentity | null; + readonly reason: string | null; +} + +export interface LaunchedExecutableObservation { + readonly selectedSha256: string | null; + readonly sha256: string | null; + readonly state: "path_metadata_unchanged" | "unknown"; + readonly reason: string | null; +} + +const identityFromStats = (stats: BigIntStats): ExecutableFileIdentity => ({ + dev: stats.dev, + ino: stats.ino, + mode: stats.mode, + size: stats.size, + mtimeNs: stats.mtimeNs, + ctimeNs: stats.ctimeNs, +}); + +const sameExecutableIdentity = ( + left: ExecutableFileIdentity, + right: ExecutableFileIdentity, +): boolean => + left.dev === right.dev && + left.ino === right.ino && + left.mode === right.mode && + left.size === right.size && + left.mtimeNs === right.mtimeNs && + left.ctimeNs === right.ctimeNs; + +const filesystemFailure = (cause: unknown): string | undefined => { + if (!(cause instanceof Error) || !("code" in cause) || !("syscall" in cause)) + return undefined; + return `Executable file observation failed: ${cause.message}`; +}; + +/** Metadata IO seam for deterministic prelaunch cancellation and identity checks. */ +export interface SelectedExecutableFileSystem { + open(path: string, flags: number): ReturnType; + stat(path: string): Promise; +} + +const selectedExecutableFileSystem: SelectedExecutableFileSystem = { + open, + stat: (path) => stat(path, { bigint: true }), +}; + +/** Sample the selected file without launching it; retain cancellation and file identity. */ +export const observeSelectedExecutable = async ( + path: string, + signal?: AbortSignal, + fileSystem: SelectedExecutableFileSystem = selectedExecutableFileSystem, +): Promise => { + assertNotCancelled(signal); + let handle: Awaited> | undefined; + try { + handle = await fileSystem.open( + path, + constants.O_RDONLY | constants.O_NONBLOCK, + ); + const openedStats = await handle.stat({ bigint: true }); + if (!openedStats.isFile()) + return { + sha256: null, + identity: null, + reason: "Selected executable is not a regular file.", + }; + const openedIdentity = identityFromStats(openedStats); + const selectedPathIdentity = identityFromStats(await fileSystem.stat(path)); + if (!sameExecutableIdentity(openedIdentity, selectedPathIdentity)) + return { + sha256: null, + identity: null, + reason: "Selected executable path changed while it was being opened.", + }; + + // IO above can settle after cancellation. Do not construct a stream with an + // already-aborted signal: Node can emit a second, unhandled AbortError. + assertNotCancelled(signal); + const hash = createHash("sha256"); + for await (const chunk of handle.createReadStream({ + autoClose: false, + signal, + })) { + assertNotCancelled(signal); + hash.update(chunk); + } + assertNotCancelled(signal); + const finalIdentity = identityFromStats( + await handle.stat({ bigint: true }), + ); + if (!sameExecutableIdentity(openedIdentity, finalIdentity)) + return { + sha256: null, + identity: null, + reason: "Selected executable changed while its contents were sampled.", + }; + return { + sha256: hash.digest("hex"), + identity: openedIdentity, + reason: null, + }; + } catch (cause: unknown) { + assertNotCancelled(signal); + const reason = filesystemFailure(cause); + if (reason !== undefined) return { sha256: null, identity: null, reason }; + throw cause; + } finally { + await handle?.close(); + } +}; + +/** Compare the selected file metadata with the path immediately after PTY spawn. */ +export const observeLaunchedExecutable = ( + path: string, + selected: SelectedExecutableObservation, +): LaunchedExecutableObservation => { + if (selected.identity === null || selected.sha256 === null) + return { + selectedSha256: selected.sha256, + sha256: null, + state: "unknown", + reason: selected.reason ?? "Selected executable identity is unavailable.", + }; + try { + const currentStats = statSync(path, { bigint: true }); + const currentIdentity = identityFromStats(currentStats); + if ( + currentStats.isFile() && + sameExecutableIdentity(selected.identity, currentIdentity) + ) + return { + selectedSha256: selected.sha256, + sha256: selected.sha256, + state: "path_metadata_unchanged", + reason: null, + }; + return { + selectedSha256: selected.sha256, + sha256: null, + state: "unknown", + reason: "Executable path metadata changed between sampling and spawn.", + }; + } catch (cause: unknown) { + const reason = filesystemFailure(cause); + if (reason !== undefined) + return { + selectedSha256: selected.sha256, + sha256: null, + state: "unknown", + reason, + }; + throw cause; + } +}; + +export const createRunManifest = async ( + scenario: ProcessScenario, + startedAt: Date, + completedAt: Date, + executable: LaunchedExecutableObservation, + host: { + readonly platform: NodeJS.Platform; + readonly architecture: NodeJS.Architecture; + } = { + platform: process.platform, + architecture: process.arch, + }, +): Promise => { + const scenarioCommitment = processScenarioCommitment( + scenario, + executable.selectedSha256 ?? undefined, + ); + const comparisonContract = processComparisonContract(scenario); + return { + rea_version: PRODUCT_IDENTITY.packageVersion, + provider_version: PROCESS_PROVIDER.version, + platform: host.platform, + architecture: host.architecture, + pty_backend: "node-pty", + started_at: startedAt.toISOString(), + completed_at: completedAt.toISOString(), + scenario: scenarioCommitment, + comparison_contract: comparisonContract, + full_scenario_sha256: digestProcessCommitment(scenarioCommitment), + comparison_contract_sha256: digestProcessCommitment(comparisonContract), + selected_executable_sha256: executable.selectedSha256, + executable_sha256: executable.sha256, + executable_identity: { + state: executable.state, + reason: executable.reason, + }, + normalization_sha256: digestProcessCommitment(scenario.normalization), + }; +}; + +export const observeSettlement = async ( + runId: string, + processGroupIds: readonly number[], + settleMs: number, + recordEvent: RecordProcessCaptureEvent = () => undefined, + platform: NodeJS.Platform = process.platform, +): Promise => { + if (platform === "win32") { + recordEvent("lifecycle", 1); + return { state: "unverifiable", elapsed_ms: 0 }; + } + const started = Date.now(); + let consecutiveEmpty = 0; + let deadlineReached = false; + while (!deadlineReached) { + const observations = await Promise.all( + [...new Set(processGroupIds)].map((processGroupId) => + observeOwnedProcessGroup({ + runId, + leaderPid: processGroupId, + processGroupId, + }), + ), + ); + if (observations.some(({ state }) => state === "unverifiable")) { + recordEvent("lifecycle", 1); + return { state: "unverifiable", elapsed_ms: Date.now() - started }; + } + if (observations.every(({ state }) => state === "empty")) { + consecutiveEmpty += 1; + if (consecutiveEmpty >= 2) { + recordEvent("lifecycle", 1); + return { state: "quiesced", elapsed_ms: Date.now() - started }; + } + } else consecutiveEmpty = 0; + deadlineReached = Date.now() - started >= settleMs; + if (deadlineReached) break; + await new Promise((resolve) => setTimeout(resolve, 50)); + } + recordEvent("lifecycle", 1); + return { state: "alive_at_deadline", elapsed_ms: Date.now() - started }; +}; + +export const awaitTerminalExit = async ({ + terminal, + scenario, + started, + lastOutput, + signal, + timers, + interactions, + dispatchedEventIndexes, + recordEvent, +}: TerminalExitOptions): Promise<{ + exitCode: number; + signal?: number; + reason: "exited" | "timeout" | "idle_timeout" | "cancelled"; +}> => + new Promise((resolveExit) => { + // The kill caused by a deadline is observed later as an ordinary PTY exit. + // Keep the initiating lifecycle reason so comparisons distinguish a target + // exit from harness-owned timeout, idle-timeout, and cancellation cleanup. + let reason: "exited" | "timeout" | "idle_timeout" | "cancelled" = "exited"; + terminal.onExit((exit) => { + recordEvent("lifecycle", 0); + for (const [eventIndex, event] of scenario.events.entries()) { + if (dispatchedEventIndexes.has(eventIndex)) continue; + const sequence = interactions.length; + interactions.push({ + sequence, + scheduled_at_ms: event.at_ms, + dispatched_at_ms: Math.max(0, Date.now() - started), + type: event.type, + data: + event.type === "input" + ? "" + : event.type === "resize" + ? `${String(event.columns)}x${String(event.rows)}` + : event.signal, + outcome: "target_exited", + }); + recordEvent("interaction_events", sequence); + } + for (const timer of timers) { + timer.cancel(); + } + timers.clear(); + resolveExit({ ...exit, reason }); + }); + const timeout = scheduleProcessInterval(() => { + if (signal?.aborted === true) { + reason = "cancelled"; + terminal.kill("SIGKILL"); + } else if (Date.now() - started >= scenario.timeout_ms) { + reason = "timeout"; + terminal.kill("SIGKILL"); + } else if (Date.now() - lastOutput() >= scenario.idle_timeout_ms) { + reason = "idle_timeout"; + terminal.kill("SIGKILL"); + } + }, 20); + timers.add(timeout); + }); + +/** Host operations used when releasing one captured process run. */ +export interface ProcessCaptureCleanupHost { + readonly platform: NodeJS.Platform; + readonly cleanupProcessGroup: typeof cleanupOwnedProcessGroup; + readonly verifyTokenOwnedProcesses: typeof verifyNoTokenOwnedProcesses; + readonly removeTemporaryRoot: (path: string) => Promise; +} + +const processCaptureCleanupHost: ProcessCaptureCleanupHost = { + platform: process.platform, + cleanupProcessGroup: cleanupOwnedProcessGroup, + verifyTokenOwnedProcesses: verifyNoTokenOwnedProcesses, + removeTemporaryRoot: (path) => rm(path, { recursive: true, force: true }), +}; + +const PROCESS_CLEANUP_VERIFICATION_GRACE_MS = 1_000; +const PROCESS_CLEANUP_VERIFICATION_INTERVAL_MS = 25; + +const verifyTokenOwnedProcessesUntilSettled = async ( + verify: () => Promise, +): Promise => { + const deadline = Date.now() + PROCESS_CLEANUP_VERIFICATION_GRACE_MS; + let result = await verify(); + while (!result.cleaned) { + const remaining = deadline - Date.now(); + if (remaining <= 0) return result; + await new Promise((resolve) => + setTimeout( + resolve, + Math.min(PROCESS_CLEANUP_VERIFICATION_INTERVAL_MS, remaining), + ), + ); + result = await verify(); + } + return result; +}; + +export const releaseProcessResources = async (options: { + readonly timers: ReadonlySet; + readonly terminal: Pick | undefined; + readonly renderer: TerminalRenderer | undefined; + readonly runId: string; + readonly temporaryRoot: string; + readonly captureBaseline?: ProcessOwnershipBaseline; + readonly sampledProcessGroupIds?: readonly number[]; + readonly host?: ProcessCaptureCleanupHost; +}): Promise => { + const host = options.host ?? processCaptureCleanupHost; + for (const timer of options.timers) timer.cancel(); + let terminalRenderer: ProcessCaptureCleanupReport["terminal_renderer"] = { + state: options.renderer === undefined ? "not_required" : "cleaned", + reason: null, + }; + try { + await options.renderer?.dispose(); + } catch (cause: unknown) { + terminalRenderer = { + state: "failed", + reason: + cause instanceof Error + ? cause.message || cause.name || "renderer dispose failed" + : "renderer dispose failed", + }; + } + let ownedProcessGroup: ProcessCaptureCleanupReport["owned_process_group"] = { + state: options.terminal === undefined ? "not_required" : "cleaned", + reason: null, + }; + if (options.terminal !== undefined) { + if (host.platform === "win32") { + ownedProcessGroup = { + state: "unverified", + reason: + "owned process cleanup is unverifiable on Windows without process-job authority", + }; + } else { + const cleaned = await host.cleanupProcessGroup({ + runId: options.runId, + leaderPid: options.terminal.pid, + processGroupId: options.terminal.pid, + sweepTokenOwnedProcesses: true, + ...(options.sampledProcessGroupIds === undefined + ? {} + : { sampledProcessGroupIds: options.sampledProcessGroupIds }), + ...(options.captureBaseline === undefined + ? {} + : { captureBaseline: options.captureBaseline }), + }); + if (!cleaned.cleaned) { + ownedProcessGroup = { state: "unverified", reason: cleaned.reason }; + } else { + const verified = await verifyTokenOwnedProcessesUntilSettled(() => + host.verifyTokenOwnedProcesses( + options.runId, + undefined, + options.captureBaseline, + ), + ); + if (!verified.cleaned) + ownedProcessGroup = { state: "unverified", reason: verified.reason }; + } + } + } + let temporaryRoot: ProcessCaptureCleanupReport["temporary_root"] = { + state: "cleaned", + reason: null, + }; + try { + await host.removeTemporaryRoot(options.temporaryRoot); + } catch (cause: unknown) { + temporaryRoot = { + state: "failed", + reason: + cause instanceof Error + ? cause.message || cause.name || "temporary root removal failed" + : "temporary root removal failed", + }; + } + return { + owned_process_group: ownedProcessGroup, + terminal_renderer: terminalRenderer, + temporary_root: temporaryRoot, + }; +}; + +export const cleanupReportFailure = ( + report: ProcessCaptureCleanupReport, +): string | undefined => { + const failures = Object.entries(report).filter( + ([, outcome]) => + outcome.state === "failed" || outcome.state === "unverified", + ); + return failures.length === 0 + ? undefined + : failures + .map( + ([resource, outcome]) => + `${resource}: ${outcome.reason ?? outcome.state}`, + ) + .join("; "); +}; + +export const captureTerminalFrames = (options: { + readonly terminal: IPty; + readonly scenario: ProcessScenario; + readonly frames: TerminalFrame[]; + readonly started: number; + readonly temporaryRoot: string; + readonly onOutput: () => void; + readonly renderer: TerminalRenderer; + readonly recordEvent: RecordProcessCaptureEvent; +}): (() => boolean) => { + let outputBytes = 0; + let truncated = false; + options.terminal.onData((data) => { + options.onOutput(); + const bytes = Buffer.byteLength(data); + if (outputBytes + bytes > options.scenario.limits.output_bytes) { + truncated = true; + return; + } + outputBytes += bytes; + const atMs = normalizeProcessElapsedTime( + Date.now() - options.started, + options.scenario.normalization.time_bucket_ms, + ); + const normalized = normalizeProcessText( + data, + options.scenario, + options.temporaryRoot, + options.terminal.pid, + ); + const sequence = options.frames.length; + options.frames.push({ + sequence, + at_ms: atMs, + data: normalized, + }); + options.renderer.write(data, atMs); + options.recordEvent("frames", sequence); + }); + return () => truncated; +}; + +export const resolveProcessResult = ( + capture: PendingProcessCapture | ProcessCapture | undefined, + executionFailure: unknown, + cleanup: ProcessCaptureCleanupReport, + observations?: IncompleteProcessCaptureObservations, + partialContext?: { + readonly scenario: ProcessScenario; + readonly rootPid?: number; + }, +): ProcessCapture => { + const cleanupFailure = cleanupReportFailure(cleanup); + if (cleanupFailure !== undefined) { + const cleanupResources = Object.entries(cleanup) + .filter( + ([, outcome]) => + outcome.state === "failed" || outcome.state === "unverified", + ) + .map(([resource]) => resource); + const executionFailureReason = + describeProcessCaptureExecutionFailure(executionFailure); + let partialObservation: PartialProcessCaptureObservation | undefined; + if (capture !== undefined || observations !== undefined) { + const normalizedObservations = + observations === undefined || partialContext === undefined + ? observations + : normalizePartialProcessObservations(observations, partialContext); + const partialPayload = + capture === undefined + ? { observations: normalizedObservations } + : { capture }; + const pendingCapture = + capture === undefined + ? undefined + : { + ...capture, + settlement: { + ...capture.settlement, + cleanup_outcome: "failed" as const, + }, + }; + const partialResult = partialProcessCaptureObservationSchema.safeParse({ + ...partialPayload, + ...(pendingCapture === undefined + ? {} + : { + capture: Object.fromEntries( + Object.entries(pendingCapture).filter( + ([key]) => key !== "cleanup", + ), + ), + }), + cleanup, + execution_failure: executionFailureReason ?? null, + }); + if (partialResult.success) partialObservation = partialResult.data; + } + throw new ProcessCaptureError(cleanupFailure, { + cause: executionFailure, + reason: "cleanup_incomplete", + cleanupResources, + ...(executionFailureReason === undefined + ? {} + : { executionFailure: executionFailureReason }), + ...(partialObservation === undefined ? {} : { partialObservation }), + cleanupReport: cleanup, + }); + } + if (executionFailure instanceof ProcessCaptureError) throw executionFailure; + if (executionFailure !== undefined) { + const executionFailureReason = + describeProcessCaptureExecutionFailure(executionFailure); + throw new ProcessCaptureError("process capture failed", { + cause: executionFailure, + ...(executionFailureReason === undefined + ? {} + : { executionFailure: executionFailureReason }), + }); + } + if (capture === undefined) + throw new ProcessCaptureError("process capture produced no result"); + if ("cleanup" in capture) return capture; + + const settlement: UnverifiedProcessCapture["settlement"] = + capture.settlement.state === "quiesced" + ? { ...capture.settlement, cleanup_outcome: "not_required" } + : { ...capture.settlement, cleanup_outcome: "cleaned" }; + try { + return parseProcessCapture({ + ...capture, + settlement, + cleanup: { + owned_process_group: "verified", + temporary_root: "removed", + }, + }); + } catch (cause: unknown) { + throw new ProcessCaptureError( + `process capture validation failed${cause instanceof Error ? `: ${cause.message}` : ""}`, + { cause }, + ); + } +}; + +export const prepareProcessCapture = async ( + scenario: ProcessScenario, + signal: AbortSignal | undefined, + captureSnapshot: typeof snapshotRoots = snapshotRoots, + host: ProcessPreparationHost = systemProcessPreparationHost, +): Promise<{ + readonly temporaryRoot: string; + readonly runId: string; + readonly ownershipBaseline: ProcessOwnershipBaseline; + readonly before: SnapshotResult; +}> => { + assertNotCancelled(signal); + await host.prepareOwnershipInspector?.(signal); + assertNotCancelled(signal); + const before = await captureSnapshot(scenario, signal); + const temporaryRoot = await host.createTemporaryRoot(); + try { + assertNotCancelled(signal); + const runId = randomUUID(); + const ownershipBaseline = + (await host.captureOwnershipBaseline?.(signal)) ?? []; + assertNotCancelled(signal); + return { temporaryRoot, runId, ownershipBaseline, before }; + } catch (cause: unknown) { + await host.cleanup(temporaryRoot); + throw cause; + } +}; + +/** Filesystem seam for allocating and cleaning a capture's temporary root. */ +export interface ProcessPreparationHost { + prepareOwnershipInspector?(signal?: AbortSignal): Promise; + /** Capture the process baseline with the caller's startup cancellation. */ + captureOwnershipBaseline?( + signal?: AbortSignal, + ): Promise; + createTemporaryRoot(): Promise; + cleanup(path: string): Promise; +} + +const systemProcessPreparationHost: ProcessPreparationHost = { + prepareOwnershipInspector: prepareProcessOwnershipInspection, + captureOwnershipBaseline: (signal) => + systemProcessOwnershipHost.captureBaseline?.(signal) ?? Promise.resolve([]), + createTemporaryRoot: () => mkdtemp(join(tmpdir(), "rea-process-")), + cleanup: (path) => rm(path, { recursive: true, force: true }), +}; diff --git a/src/application/ProcessCaptureObservation.test.ts b/src/process/capture/ProcessCaptureObservation.test.ts similarity index 70% rename from src/application/ProcessCaptureObservation.test.ts rename to src/process/capture/ProcessCaptureObservation.test.ts index 119b153cc..9f476bf26 100644 --- a/src/application/ProcessCaptureObservation.test.ts +++ b/src/process/capture/ProcessCaptureObservation.test.ts @@ -2,16 +2,13 @@ import { expect, it } from "vitest"; import { snapshotRoots } from "./FilesystemSnapshot.js"; import { buildCaptureResult } from "./ProcessCaptureLifecycle.js"; -import { normalizeProcessSamples } from "./ProcessNormalization.js"; import { isInitializedPtyRoot, readLinuxChildren } from "./ProcessSampling.js"; import { TerminalRenderer } from "./TerminalRenderer.js"; import { - compareProcessCaptures, - parseProcessCapture, parseProcessScenario, type ProcessCapture, -} from "../domain/processCapture.js"; -import { emptyProcessCapture as emptyCapture } from "../domain/processCapture.fixture.js"; +} from "../../domain/process/processCapture.js"; +import { emptyProcessCapture as emptyCapture } from "../../domain/process/processCapture.fixture.js"; const base = { executable: "/bin/sh", @@ -139,33 +136,6 @@ it("marks redacted scripted input as an interaction unknown", () => { }); }); -it("normalizes every sampled process identifier in command text", () => { - const samples = normalizeProcessSamples( - [ - { - at_ms: 0, - pid: 101, - parent_pid: 0, - process_group_id: 101, - session_id: 101, - command: "root 101", - }, - { - at_ms: 10, - pid: 202, - parent_pid: 101, - process_group_id: 101, - session_id: 101, - command: "child 202 peer=101 unrelated 1202", - }, - ], - parseProcessScenario(base), - 101, - ); - - expect(samples[1]?.command).toBe("child peer= unrelated 1202"); -}); - it("collects and deduplicates children from every Linux thread", async () => { const signal = new AbortController().signal; expect( @@ -225,35 +195,6 @@ it("admits PTY samples only after stable session and token setup", () => { ).toBe(true); }); -it("keeps interaction and process residual uncertainty in separate scopes", () => { - const baseCapture = emptyCapture(); - const interaction = parseProcessCapture({ - ...baseCapture, - residual_unknowns: [ - { scope: "interaction", reason: "Interaction capture was partial." }, - ], - }); - const process = parseProcessCapture({ - ...baseCapture, - residual_unknowns: [ - { scope: "process", reason: "Process sampling was partial." }, - ], - }); - - expect(compareProcessCaptures(interaction, baseCapture)).toMatchObject({ - status: "unknown", - terminal: "unchanged", - interaction: "unknown", - process: "unchanged", - }); - expect(compareProcessCaptures(process, baseCapture)).toMatchObject({ - status: "unknown", - terminal: "unchanged", - interaction: "unchanged", - process: "unknown", - }); -}); - it("cancels filesystem snapshots before traversing declared roots", async () => { const controller = new AbortController(); controller.abort(); @@ -267,28 +208,3 @@ it("cancels filesystem snapshots before traversing declared roots", async () => ), ).rejects.toMatchObject({ name: "AbortError" }); }); - -it("parses bounded scenarios and rejects unordered events", () => { - const timeoutMs = parseProcessScenario(base).timeout_ms; - expect(() => - parseProcessScenario({ - ...base, - events: [ - { type: "input", at_ms: 2, data: "a" }, - { type: "input", at_ms: 1, data: "b" }, - ], - }), - ).toThrow(/ordered/); - expect( - parseProcessScenario({ - ...base, - environment: { HOME: "/caller-selected-home" }, - }).environment.HOME, - ).toBe("/caller-selected-home"); - expect(() => - parseProcessScenario({ - ...base, - events: [{ type: "input", at_ms: timeoutMs + 1, data: "late" }], - }), - ).toThrow(/after the scenario timeout/); -}); diff --git a/src/application/ProcessFilesystemEffects.ts b/src/process/capture/ProcessFilesystemEffects.ts similarity index 96% rename from src/application/ProcessFilesystemEffects.ts rename to src/process/capture/ProcessFilesystemEffects.ts index a4c7112c1..59befa226 100644 --- a/src/application/ProcessFilesystemEffects.ts +++ b/src/process/capture/ProcessFilesystemEffects.ts @@ -1,7 +1,7 @@ import type { FileState, FilesystemCheckpoint, -} from "../domain/processCapture.js"; +} from "../../domain/process/processCapture.js"; /** Classify path-stable filesystem effects between two bounded states. */ export const classifyFilesystemEffects = ( diff --git a/src/process/capture/ProcessHarness.test.ts b/src/process/capture/ProcessHarness.test.ts new file mode 100644 index 000000000..4108f6162 --- /dev/null +++ b/src/process/capture/ProcessHarness.test.ts @@ -0,0 +1,357 @@ +import { expect, it, vi } from "vitest"; +import { processScenarioSchema } from "../../domain/process/processScenario.js"; +import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js"; +import { + captureProcessScenario, + normalizeCaptureFailure, + ProcessCaptureError, +} from "./ProcessHarness.js"; +import { settleProcessCaptureJournal } from "./ProcessCaptureLifecycle.js"; +import { + parseProcessCapture, + partialProcessCaptureObservationSchema, + processCaptureSchema, +} from "../../domain/process/processCapture.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../domain/process/processCapture.fixture.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; +import { emptyProcessCapture } from "../../domain/process/processCapture.fixture.js"; +import { analysisErrorProjectionSchema } from "../../contracts/errorSchemas.js"; +import { + releaseProcessResources, + resolveProcessResult, + type ProcessCaptureCleanupHost, +} from "./ProcessCaptureLifecycle.js"; +import { DarwinProcessOwnershipInspectionError } from "../DarwinProcessRunTokenReader.js"; + +it("rejects legacy replay output instead of silently discarding it", () => { + expect( + processCaptureSchema.safeParse({ + ...EMPTY_PROCESS_CAPTURE_EXAMPLE, + protocol_events: [], + }).success, + ).toBe(false); +}); + +it("preserves the actionable Darwin ownership compiler prerequisite", () => { + const normalized = normalizeCaptureFailure( + new DarwinProcessOwnershipInspectionError( + "macOS process ownership inspection requires the Apple Swift compiler via xcrun", + ), + undefined, + ); + + expect(normalized).toBeInstanceOf(ProcessCaptureError); + expect(normalized).toMatchObject({ + message: + "macOS process ownership inspection requires the Apple Swift compiler via xcrun", + }); +}); + +it("waits for terminal observations delivered after the exit callback", async () => { + const journal: Array<{ + capture_order: number; + collection: "frames"; + index: number; + }> = [{ capture_order: 0, collection: "frames", index: 0 }]; + const lateFrame = new Promise((resolve) => { + setTimeout(() => { + journal.push({ capture_order: 1, collection: "frames", index: 1 }); + resolve(); + }, 12); + }); + + let settled = false; + const wait = settleProcessCaptureJournal(journal, 20, 200).then(() => { + settled = true; + }); + await lateFrame; + expect(settled).toBe(false); + await wait; + + expect(journal).toHaveLength(2); + expect(journal[1]).toMatchObject({ collection: "frames", index: 1 }); +}); + +it("keeps empty cleanup exception messages actionable in the report", async () => { + const host: ProcessCaptureCleanupHost = { + platform: process.platform, + cleanupProcessGroup: async () => ({ + cleaned: false, + reason: "unused process cleanup", + }), + verifyTokenOwnedProcesses: async () => ({ + cleaned: false, + reason: "unused process cleanup", + }), + removeTemporaryRoot: async () => { + throw new Error(""); + }, + }; + const report = await releaseProcessResources({ + timers: new Set(), + terminal: undefined, + renderer: undefined, + runId: "fixture-run", + temporaryRoot: "/fixture/root", + host, + }); + + expect(report.temporary_root).toEqual({ state: "failed", reason: "Error" }); +}); + +it("waits for token-owned processes to exit after one cleanup signal", async () => { + vi.useFakeTimers(); + try { + let verificationCalls = 0; + const exitsAt = Date.now() + 50; + let cleanupCalls = 0; + const host: ProcessCaptureCleanupHost = { + platform: "linux", + cleanupProcessGroup: async () => { + cleanupCalls += 1; + return { cleaned: true, signaled: true }; + }, + verifyTokenOwnedProcesses: async () => { + verificationCalls += 1; + return Date.now() < exitsAt + ? { cleaned: false, reason: "owned process is exiting" } + : { cleaned: true, signaled: false }; + }, + removeTemporaryRoot: async () => undefined, + }; + const result = releaseProcessResources({ + timers: new Set(), + terminal: { pid: 321 }, + renderer: undefined, + runId: "fixture-run", + temporaryRoot: "/fixture/root", + host, + }); + + await vi.runAllTimersAsync(); + const report = await result; + + expect(report.owned_process_group).toEqual({ + state: "cleaned", + reason: null, + }); + expect(cleanupCalls).toBe(1); + expect(verificationCalls).toBeGreaterThan(1); + } finally { + vi.useRealTimers(); + } +}); + +it("retains the last ownership failure when the verification grace expires", async () => { + vi.useFakeTimers(); + try { + let verificationCalls = 0; + let cleanupCalls = 0; + const host: ProcessCaptureCleanupHost = { + platform: "linux", + cleanupProcessGroup: async () => { + cleanupCalls += 1; + return { cleaned: true, signaled: true }; + }, + verifyTokenOwnedProcesses: async () => { + verificationCalls += 1; + return { + cleaned: false, + reason: + verificationCalls === 1 + ? "owned process has not exited yet" + : "owned process remained after identity recheck", + }; + }, + removeTemporaryRoot: async () => undefined, + }; + const result = releaseProcessResources({ + timers: new Set(), + terminal: { pid: 322 }, + renderer: undefined, + runId: "fixture-run", + temporaryRoot: "/fixture/root", + host, + }); + + await vi.runAllTimersAsync(); + const report = await result; + + expect(report.owned_process_group).toEqual({ + state: "unverified", + reason: "owned process remained after identity recheck", + }); + expect(cleanupCalls).toBe(1); + expect(verificationCalls).toBeGreaterThan(1); + } finally { + vi.useRealTimers(); + } +}); + +it("passes sampled detached groups into cleanup even when their token is unknown", async () => { + let sampledGroups: readonly number[] | undefined; + const host: ProcessCaptureCleanupHost = { + platform: "linux", + cleanupProcessGroup: async (ownership) => { + sampledGroups = ownership.sampledProcessGroupIds; + return { + cleaned: false, + reason: "sampled group has no verifiable run token", + }; + }, + verifyTokenOwnedProcesses: async () => ({ + cleaned: true, + signaled: false, + }), + removeTemporaryRoot: async () => undefined, + }; + + const report = await releaseProcessResources({ + timers: new Set(), + terminal: { pid: 321 }, + renderer: undefined, + runId: "fixture-run", + temporaryRoot: "/fixture/root", + sampledProcessGroupIds: [654], + host, + }); + + expect(sampledGroups).toEqual([654]); + expect(report.owned_process_group).toEqual({ + state: "unverified", + reason: "sampled group has no verifiable run token", + }); +}); + +it("retains observations and both causes when process cleanup is unverifiable", () => { + const verified = emptyProcessCapture(); + const capture = parseProcessCapture({ + ...verified, + frames: [{ sequence: 0, at_ms: 0, data: "observed output" }], + process_samples: [ + { + at_ms: 1, + pid: 654, + parent_pid: 321, + command: "sanitized-detached-child", + process_group_id: 654, + session_id: 654, + }, + ], + event_journal: [], + }); + const executionFailure = new Error("capture ended after a fixture error"); + const cleanup = { + owned_process_group: { + state: "unverified" as const, + reason: + "process ownership token could not be read for 1 live process(es): environment_unavailable=1", + }, + terminal_renderer: { state: "cleaned" as const, reason: null }, + temporary_root: { state: "cleaned" as const, reason: null }, + }; + + let error: ProcessCaptureError | undefined; + try { + resolveProcessResult(capture, executionFailure, cleanup); + } catch (cause: unknown) { + if (!(cause instanceof ProcessCaptureError)) throw cause; + error = cause; + } + expect(error).toBeDefined(); + if (error === undefined) throw new Error("expected cleanup-incomplete error"); + + const projection = projectAnalysisError(error); + const parsedProjection = analysisErrorProjectionSchema.parse(projection); + expect(parsedProjection).toMatchObject({ + code: "cleanup_incomplete", + details: { + cleanup_report: cleanup, + execution_failure: "capture ended after a fixture error", + partial_observation: { + capture: { + frames: [{ data: "observed output" }], + process_samples: [{ pid: 654, process_group_id: 654 }], + settlement: { cleanup_outcome: "failed" }, + }, + execution_failure: "capture ended after a fixture error", + }, + }, + }); + expect(error.cause).toBe(executionFailure); + const partialObservation = error.partialObservation; + expect(partialObservation).toBeDefined(); + if (partialObservation === undefined) + throw new Error("expected validated partial observation"); + expect( + partialProcessCaptureObservationSchema.safeParse({ + ...partialObservation, + cleanup: { + owned_process_group: { state: "cleaned", reason: null }, + terminal_renderer: { state: "cleaned", reason: null }, + temporary_root: { state: "cleaned", reason: null }, + }, + }).success, + ).toBe(false); + if (!("capture" in partialObservation)) + throw new Error("expected completed partial capture observations"); + const partialCapture = partialObservation.capture; + expect(() => parseProcessCapture(partialCapture)).toThrow(); +}); + +it("projects execution and cleanup failures when capture never completed", () => { + const executionFailure = new Error("terminal startup failed"); + const cleanup = { + owned_process_group: { + state: "unverified" as const, + reason: "process ownership token could not be read", + }, + terminal_renderer: { state: "cleaned" as const, reason: null }, + temporary_root: { state: "cleaned" as const, reason: null }, + }; + + let error: ProcessCaptureError | undefined; + try { + resolveProcessResult(undefined, executionFailure, cleanup); + } catch (cause: unknown) { + if (!(cause instanceof ProcessCaptureError)) throw cause; + error = cause; + } + expect(error).toBeDefined(); + if (error === undefined) throw new Error("expected cleanup-incomplete error"); + + expect(error.partialObservation).toBeUndefined(); + expect(error.cause).toBe(executionFailure); + expect(projectAnalysisError(error)).toMatchObject({ + code: "cleanup_incomplete", + details: { + cleanup_report: cleanup, + execution_failure: "terminal startup failed", + }, + }); +}); + +it("fails closed on Windows before resolving or launching scenario paths", async () => { + const scenario = processScenarioSchema.parse({ + executable: "Z:/missing/should-never-be-resolved.exe", + working_directory: "Z:/missing/working-directory", + }); + const result = await captureProcessScenario(scenario, undefined, "win32"); + expect(result.ok).toBe(false); + if (result.ok) throw new Error("expected Windows ownership refusal"); + if (!(result.error instanceof AnalysisCapabilityUnavailableError)) + throw new Error("expected a capability-unavailable outcome"); + expect(result.error).toMatchObject({ + operation: "capture_process_scenario", + reason: expect.stringContaining("Windows PTY process capture"), + }); + expect(projectAnalysisError(result.error)).toMatchObject({ + code: "capability_unavailable", + category: "unsupported_provider", + message: expect.stringContaining("does not yet verify descendant cleanup"), + details: { + operation: "capture_process_scenario", + reason: result.error.reason, + }, + }); +}); diff --git a/src/application/ProcessHarness.ts b/src/process/capture/ProcessHarness.ts similarity index 53% rename from src/application/ProcessHarness.ts rename to src/process/capture/ProcessHarness.ts index 822f8f989..3d7af9c80 100644 --- a/src/application/ProcessHarness.ts +++ b/src/process/capture/ProcessHarness.ts @@ -8,12 +8,13 @@ import type { ProcessScenario, RecordProcessCaptureEvent, TerminalFrame, -} from "../domain/processCapture.js"; -import { parseProcessCapture } from "../domain/processCapture.js"; -import { err, ok, type Result } from "../domain/result.js"; -import { AnalysisCapabilityUnavailableError } from "../domain/analysisErrorCore.js"; -import { type AnalysisError } from "../domain/analysisErrorBase.js"; +} from "../../domain/process/processCapture.js"; +import { parseProcessCapture } from "../../domain/process/processCapture.js"; +import { err, ok, type Result } from "../../domain/result.js"; +import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js"; +import { type AnalysisError } from "../../domain/analysisErrorBase.js"; import { + describeProcessCaptureExecutionFailure, ProcessCaptureError, processCaptureCancelled, } from "./ProcessCaptureError.js"; @@ -23,17 +24,23 @@ import { snapshotRoots } from "./FilesystemSnapshot.js"; import { TerminalRenderer } from "./TerminalRenderer.js"; import type { ProcessTimer } from "./ProcessTimer.js"; import { normalizeProcessText } from "./ProcessNormalization.js"; -import { selectCapturedProcessGroupIds } from "../process/ProcessOwnershipProcessTree.js"; +import { selectCapturedProcessGroupIds } from "../ProcessOwnershipProcessTree.js"; import { awaitTerminalExit, buildCaptureResult, + cleanupReportFailure, captureTerminalFrames, + createProcessCaptureObservationBuffer, createRunManifest, + observeLaunchedExecutable, + observeSelectedExecutable, observeSettlement, prepareProcessCapture, releaseProcessResources, resolveProcessResult, settleProcessCaptureJournal, + type ProcessCaptureCleanupHost, + type ProcessCaptureObservationBuffer, type PendingProcessCapture, } from "./ProcessCaptureLifecycle.js"; import { @@ -47,6 +54,8 @@ import { import { makeProcessCaptureEnvironment } from "./ProcessCaptureEnvironment.js"; import { classifyFilesystemEffects } from "./ProcessFilesystemEffects.js"; import { processCaptureOwnershipUnavailableReason } from "./ProcessCaptureCapability.js"; +import type { ProcessOwnershipBaseline } from "../ProcessOwnership.js"; +import { DarwinProcessOwnershipInspectionError } from "../DarwinProcessRunTokenReader.js"; export { probeProcessCaptureCapability } from "./ProcessCaptureCapability.js"; interface StartedCaptureRuntime { @@ -54,6 +63,7 @@ interface StartedCaptureRuntime { readonly terminal: IPty; readonly started: number; readonly startedAt: Date; + readonly executableIdentity: ReturnType; readonly lastOutput: () => number; readonly framesTruncated: () => boolean; readonly stopSampler: () => Promise<{ readonly partial: boolean }>; @@ -65,19 +75,58 @@ const cleanupFailedStartup = async (options: { readonly terminal: IPty | undefined; readonly renderer: TerminalRenderer | undefined; readonly runId: string; + readonly captureBaseline: ProcessOwnershipBaseline; + readonly scenario: ProcessScenario; + readonly cleanupHost?: ProcessCaptureCleanupHost; + readonly observations?: ProcessCaptureObservationBuffer; readonly temporaryRoot: string; }): Promise => { + const sampledProcessGroupIds = + options.terminal !== undefined && + options.observations?.process_samples.state === "available" + ? selectCapturedProcessGroupIds( + options.terminal.pid, + options.observations.process_samples.value, + ).filter((groupId) => groupId !== options.terminal?.pid) + : undefined; options.terminal?.kill("SIGKILL"); + if (options.observations !== undefined && options.renderer !== undefined) { + try { + options.observations.rendered_frames = { + state: "available", + value: await options.renderer.frames(), + }; + } catch (cause: unknown) { + options.observations.rendered_frames = { + state: "unavailable", + reason: `Rendered terminal frame collection failed: ${cause instanceof Error ? cause.message : "unknown failure"}`, + }; + } + } const cleanupFailure = await releaseProcessResources({ - ...options, - capturedProcessGroupIds: - options.terminal === undefined ? [] : [options.terminal.pid], + timers: options.timers, + terminal: options.terminal, + renderer: options.renderer, + runId: options.runId, + captureBaseline: options.captureBaseline, + ...(sampledProcessGroupIds === undefined ? {} : { sampledProcessGroupIds }), + temporaryRoot: options.temporaryRoot, + ...(options.cleanupHost === undefined ? {} : { host: options.cleanupHost }), }); - if (cleanupFailure !== undefined) - throw new ProcessCaptureError(cleanupFailure, { - cause: options.cause, - reason: "cleanup_incomplete", - }); + if (cleanupReportFailure(cleanupFailure) !== undefined) { + resolveProcessResult( + undefined, + options.cause, + cleanupFailure, + options.observations, + { + scenario: options.scenario, + ...(options.terminal === undefined + ? {} + : { rootPid: options.terminal.pid }), + }, + ); + } throw options.cause; }; @@ -102,6 +151,10 @@ interface StartCaptureRuntimeOptions { readonly hostEnvironment: Readonly>; readonly temporaryRoot: string; readonly runId: string; + readonly ownershipBaseline: ProcessOwnershipBaseline; + readonly cleanupHost?: ProcessCaptureCleanupHost; + readonly observationBuffer: ProcessCaptureObservationBuffer; + readonly onSpawn: (pid: number) => void; readonly frames: TerminalFrame[]; readonly samples: ProcessSample[]; readonly interactions: InteractionEvent[]; @@ -119,6 +172,10 @@ const startCaptureRuntime = async ( let terminal: IPty | undefined; try { const { spawn } = await import("@lydell/node-pty"); + const selectedExecutable = await observeSelectedExecutable( + scenario.executable, + options.signal, + ); const startedAt = new Date(); const started = Date.now(); let lastOutput = started; @@ -135,6 +192,15 @@ const startCaptureRuntime = async ( rows: scenario.terminal.rows, name: "xterm-256color", }); + options.onSpawn(terminal.pid); + options.observationBuffer.target_pid = { + state: "available", + value: scenario.normalization.pids ? 1 : terminal.pid, + }; + const executableIdentity = observeLaunchedExecutable( + scenario.executable, + selectedExecutable, + ); const framesTruncated = captureTerminalFrames({ ...options, terminal, @@ -161,6 +227,7 @@ const startCaptureRuntime = async ( terminal, started, startedAt, + executableIdentity, lastOutput: () => lastOutput, framesTruncated, stopSampler, @@ -172,6 +239,12 @@ const startCaptureRuntime = async ( terminal, renderer, runId: options.runId, + captureBaseline: options.ownershipBaseline, + scenario, + ...(options.cleanupHost === undefined + ? {} + : { cleanupHost: options.cleanupHost }), + observations: options.observationBuffer, temporaryRoot: options.temporaryRoot, }); } @@ -181,27 +254,36 @@ const finishProcessRun = async (options: { readonly runtime: StartedCaptureRuntime | undefined; readonly timers: Set; readonly runId: string; + readonly captureBaseline: ProcessOwnershipBaseline; + readonly cleanupHost?: ProcessCaptureCleanupHost; readonly temporaryRoot: string; + readonly scenario: ProcessScenario; readonly samples: readonly ProcessSample[]; readonly stopSampler: () => Promise<{ readonly partial: boolean }>; readonly capture: PendingProcessCapture | undefined; readonly executionFailure: unknown; + readonly observations?: ProcessCaptureObservationBuffer; + readonly rootPid?: number; }): Promise => { await options.stopSampler(); - const cleanupFailure = await releaseProcessResources({ + const sampledProcessGroupIds = + options.runtime === undefined + ? undefined + : selectCapturedProcessGroupIds( + options.runtime.terminal.pid, + options.samples, + ).filter((groupId) => groupId !== options.runtime?.terminal.pid); + const cleanup = await releaseProcessResources({ timers: options.timers, terminal: options.runtime?.terminal, renderer: options.runtime?.renderer, runId: options.runId, + captureBaseline: options.captureBaseline, + ...(sampledProcessGroupIds === undefined ? {} : { sampledProcessGroupIds }), temporaryRoot: options.temporaryRoot, - capturedProcessGroupIds: - options.runtime === undefined - ? [] - : selectCapturedProcessGroupIds( - options.runtime.terminal.pid, - options.samples, - ), + ...(options.cleanupHost === undefined ? {} : { host: options.cleanupHost }), }); + const cleanupFailure = cleanupReportFailure(cleanup); let { capture, executionFailure } = options; let verifiedCapture: ProcessCapture | undefined; if (capture !== undefined && cleanupFailure === undefined) { @@ -226,13 +308,37 @@ const finishProcessRun = async (options: { ); } } + const rootPid = options.runtime?.terminal.pid ?? options.rootPid; + const partialContext = + rootPid === undefined + ? { scenario: options.scenario } + : { scenario: options.scenario, rootPid }; return resolveProcessResult( - verifiedCapture, + cleanupFailure === undefined ? verifiedCapture : capture, executionFailure, - cleanupFailure, + cleanup, + options.observations, + partialContext, ); }; +/** Preserve typed process-inspection preflight failures at the capture boundary. */ +export const normalizeCaptureFailure = ( + cause: unknown, + signal: AbortSignal | undefined, +): unknown => { + if (cause instanceof ProcessCaptureError) return cause; + if (cause instanceof DarwinProcessOwnershipInspectionError) + return new ProcessCaptureError(cause.message, { cause }); + if ( + signal?.aborted === true && + (cause === signal.reason || + (cause instanceof Error && cause.name === "AbortError")) + ) + return processCaptureCancelled(); + return cause; +}; + const completeCapture = async (options: { readonly scenario: ProcessScenario; readonly hostPlatform: NodeJS.Platform; @@ -245,26 +351,75 @@ const completeCapture = async (options: { readonly interactions: readonly InteractionEvent[]; readonly exit: Awaited>; readonly signal?: AbortSignal; + readonly captureSnapshot: typeof snapshotRoots; readonly initiallyTruncated: boolean; readonly eventJournal: readonly ProcessCaptureEventJournalEntry[]; + readonly observationBuffer: ProcessCaptureObservationBuffer; readonly recordEvent: RecordProcessCaptureEvent; }): Promise => { + const unavailableReason = (stage: string, cause: unknown): string => + `${stage} failed: ${cause instanceof Error ? cause.message : "unknown failure"}`; const { runtime, scenario } = options; const { reason } = options.exit; if (reason === "cancelled") throw processCaptureCancelled(); - const settlement = await observeSettlement( - options.runId, - selectCapturedProcessGroupIds(runtime.terminal.pid, options.samples), - scenario.settle_ms, - options.recordEvent, - options.hostPlatform, - ); + let settlement: Awaited>; + try { + settlement = await observeSettlement( + options.runId, + selectCapturedProcessGroupIds(runtime.terminal.pid, options.samples), + scenario.settle_ms, + options.recordEvent, + options.hostPlatform, + ); + } catch (cause: unknown) { + options.observationBuffer.settlement = { + state: "unavailable", + reason: unavailableReason("Process settlement observation", cause), + }; + throw cause; + } + options.observationBuffer.settlement = { + state: "available", + value: settlement, + }; const samplingPartial = (await runtime.stopSampler()).partial; await settleProcessCaptureJournal(options.eventJournal); assertNotCancelled(options.signal); - const after = await snapshotRoots(scenario, options.signal); + let after: Awaited>; + try { + after = await options.captureSnapshot(scenario, options.signal); + } catch (cause: unknown) { + options.observationBuffer.filesystem_snapshots = { + ...options.observationBuffer.filesystem_snapshots, + after: { + state: "unavailable", + reason: unavailableReason("Final filesystem snapshot", cause), + }, + }; + throw cause; + } + options.observationBuffer.filesystem_snapshots = { + ...options.observationBuffer.filesystem_snapshots, + after: { + state: "available", + value: { files: after.files, truncated: after.truncated }, + }, + }; options.recordEvent("filesystem_checkpoints", 1); - const renderedFrames = await runtime.renderer.frames(); + let renderedFrames: Awaited>; + try { + renderedFrames = await runtime.renderer.frames(); + } catch (cause: unknown) { + options.observationBuffer.rendered_frames = { + state: "unavailable", + reason: unavailableReason("Rendered terminal frame collection", cause), + }; + throw cause; + } + options.observationBuffer.rendered_frames = { + state: "available", + value: renderedFrames, + }; const checkpoints: UnverifiedProcessCapture["filesystem_checkpoints"] = [ { name: "before", @@ -281,12 +436,23 @@ const completeCapture = async (options: { truncated: after.truncated, }, ]; - const manifest = await createRunManifest( - scenario, - runtime.startedAt, - new Date(), - { platform: options.hostPlatform, architecture: process.arch }, - ); + let manifest: Awaited>; + try { + manifest = await createRunManifest( + scenario, + runtime.startedAt, + new Date(), + runtime.executableIdentity, + { platform: options.hostPlatform, architecture: process.arch }, + ); + } catch (cause: unknown) { + options.observationBuffer.manifest = { + state: "unavailable", + reason: unavailableReason("Capture manifest creation", cause), + }; + throw cause; + } + options.observationBuffer.manifest = { state: "available", value: manifest }; const truncated = options.initiallyTruncated || after.truncated || @@ -319,22 +485,30 @@ const runProcessScenario = async ( signal?: AbortSignal, hostEnvironment: Readonly> = process.env, hostPlatform: NodeJS.Platform = process.platform, + captureSnapshot: typeof snapshotRoots = snapshotRoots, + cleanupHost?: ProcessCaptureCleanupHost, ): Promise => { - const { temporaryRoot, runId, before } = await prepareProcessCapture( - scenario, - signal, - ); + const { temporaryRoot, runId, ownershipBaseline, before } = + await prepareProcessCapture(scenario, signal, captureSnapshot); const frames: TerminalFrame[] = []; const samples: ProcessSample[] = []; + const interactions: InteractionEvent[] = []; const journal = createProcessCaptureJournal(); const { entries: eventJournal, recordEvent } = journal; recordEvent("filesystem_checkpoints", 0); + const observations = createProcessCaptureObservationBuffer({ + frames, + interactions, + samples, + eventJournal, + before, + }); let runtime: StartedCaptureRuntime | undefined; + let actualRootPid: number | undefined; const timers = new Set(); let capture: PendingProcessCapture | undefined; let executionFailure: unknown; let stopSampler = async () => ({ partial: false }); - const interactions: InteractionEvent[] = []; const dispatchedEventIndexes = new Set(); try { @@ -343,6 +517,12 @@ const runProcessScenario = async ( hostEnvironment, temporaryRoot, runId, + ownershipBaseline, + ...(cleanupHost === undefined ? {} : { cleanupHost }), + observationBuffer: observations, + onSpawn: (pid) => { + actualRootPid = pid; + }, frames, samples, interactions, @@ -363,6 +543,15 @@ const runProcessScenario = async ( dispatchedEventIndexes, recordEvent, }); + observations.exit = { + state: "available", + value: { + code: + exit.reason === "exited" && exit.exitCode >= 0 ? exit.exitCode : null, + signal: exit.signal ?? null, + reason: exit.reason, + }, + }; capture = await completeCapture({ scenario, hostPlatform, @@ -376,22 +565,52 @@ const runProcessScenario = async ( exit, initiallyTruncated: before.truncated, eventJournal, + observationBuffer: observations, recordEvent, + captureSnapshot, ...(signal === undefined ? {} : { signal }), }); } catch (cause: unknown) { runtime?.terminal.kill("SIGKILL"); - executionFailure = cause; + executionFailure = normalizeCaptureFailure(cause, signal); + if ( + runtime !== undefined && + observations.rendered_frames.state === "unavailable" && + observations.rendered_frames.reason.startsWith( + "Rendered terminal frames were not collected", + ) + ) { + try { + observations.rendered_frames = { + state: "available", + value: await runtime.renderer.frames(), + }; + } catch (renderCause: unknown) { + const renderFailure = + renderCause instanceof Error + ? renderCause.message + : "unknown failure"; + observations.rendered_frames = { + state: "unavailable", + reason: `Rendered terminal frame collection failed: ${renderFailure}`, + }; + } + } } return finishProcessRun({ runtime, timers, runId, + captureBaseline: ownershipBaseline, + ...(cleanupHost === undefined ? {} : { cleanupHost }), temporaryRoot, + scenario, samples, stopSampler, capture, executionFailure, + observations, + ...(actualRootPid === undefined ? {} : { rootPid: actualRootPid }), }); }; @@ -401,6 +620,8 @@ export const captureProcessScenario = async ( signal?: AbortSignal, platform: NodeJS.Platform = process.platform, environment: Readonly> = process.env, + captureSnapshot: typeof snapshotRoots = snapshotRoots, + cleanupHost?: ProcessCaptureCleanupHost, ): Promise> => { const ownershipReason = processCaptureOwnershipUnavailableReason(platform); if (ownershipReason !== undefined) @@ -419,13 +640,28 @@ export const captureProcessScenario = async ( environment, ); return ok( - await runProcessScenario(resolvedScenario, signal, environment, platform), + await runProcessScenario( + resolvedScenario, + signal, + environment, + platform, + captureSnapshot, + cleanupHost, + ), ); } catch (cause: unknown) { + const failure = normalizeCaptureFailure(cause, signal); + const executionFailureReason = + describeProcessCaptureExecutionFailure(cause); return err( - cause instanceof ProcessCaptureError - ? cause - : new ProcessCaptureError("process capture failed", { cause }), + failure instanceof ProcessCaptureError + ? failure + : new ProcessCaptureError("process capture failed", { + cause, + ...(executionFailureReason === undefined + ? {} + : { executionFailure: executionFailureReason }), + }), ); } }; diff --git a/src/process/capture/ProcessNormalization.test.ts b/src/process/capture/ProcessNormalization.test.ts new file mode 100644 index 000000000..7a15b8b96 --- /dev/null +++ b/src/process/capture/ProcessNormalization.test.ts @@ -0,0 +1,159 @@ +import { expect, it } from "vitest"; + +import { normalizeProcessSamples } from "./ProcessNormalization.js"; +import { parseProcessScenario } from "../../domain/process/processCapture.js"; + +const rootPid = 41_010; +const samples = Object.freeze( + [ + { + at_ms: 17, + pid: rootPid, + parent_pid: 40_000, + process_group_id: rootPid, + session_id: rootPid, + command: " root 41010 ", + }, + { + at_ms: 28, + pid: 42_020, + parent_pid: rootPid, + process_group_id: 43_030, + session_id: 44_040, + command: + " /workspace/program child 42020 parent 41010 group 43030 session 44040 listen:8080 marker ", + }, + { + at_ms: 39, + pid: 45_050, + parent_pid: 42_020, + process_group_id: 43_030, + session_id: 44_040, + command: "peer 45050 unrelated 145050", + }, + { + at_ms: 41, + pid: 46_060, + parent_pid: 0, + process_group_id: null, + session_id: 0, + command: "unknown 46060", + }, + { + at_ms: 52, + pid: 47_070, + parent_pid: 0, + process_group_id: 0, + session_id: null, + command: "unavailable 47070", + }, + ].map((sample) => + Object.freeze({ ...sample, runtime_metadata: "must not be serialized" }), + ), +); + +const scenario = (normalization: { + readonly pids?: boolean; + readonly paths?: boolean; + readonly ports?: boolean; +}) => + parseProcessScenario({ + executable: "/usr/bin/node", + working_directory: "/workspace", + normalization: { + paths: true, + ...normalization, + time_bucket_ms: 10, + patterns: [{ pattern: "marker", replacement: "selected" }], + }, + }); + +it("normalizes representative process identities and preserves null, zero, and unrelated numbers", () => { + const before = JSON.stringify(samples); + const normalized = normalizeProcessSamples(samples, scenario({}), rootPid); + + expect(normalized).toEqual([ + { + at_ms: 10, + pid: 1, + parent_pid: 2, + process_group_id: 1, + session_id: 1, + command: "root ", + }, + { + at_ms: 20, + pid: 3, + parent_pid: 1, + process_group_id: 4, + session_id: 5, + command: + "/program child parent group session listen: selected", + }, + { + at_ms: 30, + pid: 6, + parent_pid: 3, + process_group_id: 4, + session_id: 5, + command: "peer unrelated 145050", + }, + { + at_ms: 40, + pid: 7, + parent_pid: 0, + process_group_id: null, + session_id: 0, + command: "unknown ", + }, + { + at_ms: 50, + pid: 8, + parent_pid: 0, + process_group_id: 0, + session_id: null, + command: "unavailable ", + }, + ]); + expect(JSON.stringify(samples)).toBe(before); + expect(normalized.every((sample) => !("runtime_metadata" in sample))).toBe( + true, + ); + + const pathAndPortPreserved = normalizeProcessSamples( + samples, + scenario({ paths: false, ports: false }), + rootPid, + ); + expect(pathAndPortPreserved[1]).toMatchObject({ + pid: 3, + parent_pid: 1, + command: + "/workspace/program child parent group session listen:8080 selected", + }); + + const identifiersPreserved = normalizeProcessSamples( + samples, + scenario({ pids: false }), + rootPid, + ); + expect( + identifiersPreserved.map( + ({ pid, parent_pid, process_group_id, session_id }) => [ + pid, + parent_pid, + process_group_id, + session_id, + ], + ), + ).toEqual([ + [rootPid, 40_000, rootPid, rootPid], + [42_020, rootPid, 43_030, 44_040], + [45_050, 42_020, 43_030, 44_040], + [46_060, 0, null, 0], + [47_070, 0, 0, null], + ]); + expect(identifiersPreserved[1]?.command).toBe( + "/program child 42020 parent 41010 group 43030 session 44040 listen: selected", + ); +}); diff --git a/src/application/ProcessNormalization.ts b/src/process/capture/ProcessNormalization.ts similarity index 98% rename from src/application/ProcessNormalization.ts rename to src/process/capture/ProcessNormalization.ts index aff05d33a..347c0e5cf 100644 --- a/src/application/ProcessNormalization.ts +++ b/src/process/capture/ProcessNormalization.ts @@ -1,7 +1,7 @@ import type { ProcessSample, ProcessScenario, -} from "../domain/processCapture.js"; +} from "../../domain/process/processCapture.js"; /** Bucket one elapsed process-capture timestamp under scenario normalization. */ export const normalizeProcessElapsedTime = ( diff --git a/src/application/ProcessSampling.ts b/src/process/capture/ProcessSampling.ts similarity index 98% rename from src/application/ProcessSampling.ts rename to src/process/capture/ProcessSampling.ts index b559a8f7a..e8ee7cefb 100644 --- a/src/application/ProcessSampling.ts +++ b/src/process/capture/ProcessSampling.ts @@ -2,9 +2,9 @@ import { readFile, readdir } from "node:fs/promises"; import type { ProcessSample, RecordProcessCaptureEvent, -} from "../domain/processCapture.js"; -import { execFileOutput } from "../process/ExecFileOutput.js"; -import { readProcessRunId } from "../process/ProcessOwnership.js"; +} from "../../domain/process/processCapture.js"; +import { execFileOutput } from "../ExecFileOutput.js"; +import { readProcessRunId } from "../ProcessOwnership.js"; const PROC_READ_CONCURRENCY = 64; interface ProcessRow { diff --git a/src/application/ProcessScenarioRuntimeValidation.test.ts b/src/process/capture/ProcessScenarioRuntimeValidation.test.ts similarity index 93% rename from src/application/ProcessScenarioRuntimeValidation.test.ts rename to src/process/capture/ProcessScenarioRuntimeValidation.test.ts index 922df07ee..5e70a226c 100644 --- a/src/application/ProcessScenarioRuntimeValidation.test.ts +++ b/src/process/capture/ProcessScenarioRuntimeValidation.test.ts @@ -7,7 +7,7 @@ import { resolveExecutable, resolveProcessScenarioRuntimePaths, } from "./ProcessScenarioRuntimeValidation.js"; -import { parseProcessScenario } from "../domain/processCapture.js"; +import { parseProcessScenario } from "../../domain/process/processCapture.js"; it("preserves the caller-selected executable symlink for process invocation", async () => { const root = await mkdtemp(join(tmpdir(), "rea-process-executable-alias-")); diff --git a/src/application/ProcessScenarioRuntimeValidation.ts b/src/process/capture/ProcessScenarioRuntimeValidation.ts similarity index 97% rename from src/application/ProcessScenarioRuntimeValidation.ts rename to src/process/capture/ProcessScenarioRuntimeValidation.ts index 59cddcf24..e71e16d1e 100644 --- a/src/application/ProcessScenarioRuntimeValidation.ts +++ b/src/process/capture/ProcessScenarioRuntimeValidation.ts @@ -2,7 +2,7 @@ import { access, realpath, stat } from "node:fs/promises"; import { constants as fsConstants } from "node:fs"; import { delimiter, isAbsolute, join, resolve } from "node:path"; -import type { ProcessScenario } from "../domain/processCapture.js"; +import type { ProcessScenario } from "../../domain/process/processCapture.js"; import { ProcessCaptureError, processCaptureCancelled, diff --git a/src/application/ProcessTimer.test.ts b/src/process/capture/ProcessTimer.test.ts similarity index 100% rename from src/application/ProcessTimer.test.ts rename to src/process/capture/ProcessTimer.test.ts diff --git a/src/application/ProcessTimer.ts b/src/process/capture/ProcessTimer.ts similarity index 100% rename from src/application/ProcessTimer.ts rename to src/process/capture/ProcessTimer.ts diff --git a/src/application/TerminalRenderer.ts b/src/process/capture/TerminalRenderer.ts similarity index 98% rename from src/application/TerminalRenderer.ts rename to src/process/capture/TerminalRenderer.ts index 44bf4d580..0a0492008 100644 --- a/src/application/TerminalRenderer.ts +++ b/src/process/capture/TerminalRenderer.ts @@ -3,7 +3,7 @@ import { createRequire } from "node:module"; import type { RecordProcessCaptureEvent, RenderedTerminalFrame, -} from "../domain/processCapture.js"; +} from "../../domain/process/processCapture.js"; const require = createRequire(import.meta.url); // SAFETY: both pinned xterm packages publish CommonJS at runtime and matching declarations. diff --git a/src/server/createServer.ts b/src/server/createServer.ts index 68b1aeb08..acc5dca05 100644 --- a/src/server/createServer.ts +++ b/src/server/createServer.ts @@ -25,6 +25,9 @@ import type { WebSourceLocationService } from "../application/WebSourceLocationS import type { WebRuntimeService } from "../application/WebRuntimeService.js"; import { createWebRuntimeService } from "../composition/webRuntime.js"; import { registerWebRuntimeTools } from "./registerWebRuntimeTools.js"; +import type { WebNetworkCaptureService } from "../application/WebNetworkCaptureService.js"; +import { createWebNetworkCaptureService } from "../composition/webNetworkCaptures.js"; +import { registerWebNetworkCaptureTool } from "./registerWebNetworkCaptureTool.js"; import { registerJavaScriptRecoveryTool } from "./registerJavaScriptRecoveryTool.js"; import { JavaScriptRecoveryService } from "../application/javascript/JavaScriptRecoveryService.js"; import type { JavaScriptRecoveryPort } from "../application/javascript/JavaScriptRecoveryPort.js"; @@ -62,6 +65,7 @@ export interface CreateServerOptions { readonly webModuleTrace?: WebModuleTraceService; readonly webSourceLocation?: WebSourceLocationService; readonly webRuntime?: WebRuntimeService; + readonly webNetworkCapture?: WebNetworkCaptureService; readonly androidAnalysis?: AndroidAnalysisPort; readonly browserObservation?: BrowserObservationPort; readonly browserScenarioCapture?: BrowserScenarioCapturePort; @@ -213,6 +217,12 @@ export const createServer = ( toolLogger, recordEvidence, ); + registerWebNetworkCaptureTool( + server, + options.webNetworkCapture ?? createWebNetworkCaptureService(), + toolLogger, + recordEvidence, + ); registerJavaScriptRecoveryTool( server, new JavaScriptRecoveryService( diff --git a/src/server/promptCompletion.ts b/src/server/promptCompletion.ts index b02c5a35b..bb5193009 100644 --- a/src/server/promptCompletion.ts +++ b/src/server/promptCompletion.ts @@ -7,7 +7,7 @@ import type { BinarySessionPort } from "../application/binary/BinarySession.js"; import type { PromptCompletionKind } from "../contracts/promptContracts.js"; import { artifactInventoryResultSchema } from "../domain/artifactGraph.js"; import { artifactInspectionResultSchema } from "../domain/artifactInspection.js"; -import { processCaptureSchema } from "../domain/processCapture.js"; +import { parseProcessCapture } from "../domain/process/processCapture.js"; const documentListSchema = z.array(z.string().min(1)); const providerStatusSchema = z.object({ @@ -171,8 +171,13 @@ const evidenceValues = ( const isProcessCaptureEvidence = (evidence: LedgerEvidence): boolean => { if (evidence.operation !== "capture_process_scenario") return false; - const capture = processCaptureSchema.safeParse(evidence.normalized_result); - return capture.success; + try { + parseProcessCapture(evidence.normalized_result); + return true; + } catch (cause: unknown) { + if (cause instanceof TypeError || cause instanceof z.ZodError) return false; + throw cause; + } }; const unknownCandidates = ( diff --git a/src/server/registerEvidenceTools.ts b/src/server/registerEvidenceTools.ts index e906bd862..be678179b 100644 --- a/src/server/registerEvidenceTools.ts +++ b/src/server/registerEvidenceTools.ts @@ -14,7 +14,7 @@ import { mcpProgressReporter } from "./mcpProgress.js"; import { logToolExecution } from "./toolLogging.js"; import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; import { toCallToolResult } from "./toolResult.js"; -import { createArtifactExtractionDestination } from "../application/ArtifactExtractionDestination.js"; +import { createArtifactExtractionDestination } from "../application/artifacts/ArtifactExtractionDestination.js"; interface EvidenceToolRegistration { readonly logger: Logger; diff --git a/src/server/registerProcessComparisonTool.ts b/src/server/registerProcessComparisonTool.ts index 868528322..3b47d9c42 100644 --- a/src/server/registerProcessComparisonTool.ts +++ b/src/server/registerProcessComparisonTool.ts @@ -13,7 +13,7 @@ import { jsonValueSchema } from "../domain/jsonValue.js"; import { compareProcessCaptures, parseProcessCapture, -} from "../domain/processCapture.js"; +} from "../domain/process/processCapture.js"; import type { RecordUnknownInput } from "../domain/residualUnknown.js"; import { err } from "../domain/result.js"; import { recordDerivedEvidence } from "./recordDerivedEvidence.js"; diff --git a/src/server/registerSessionTools.ts b/src/server/registerSessionTools.ts index fd601bfcf..fb6feb548 100644 --- a/src/server/registerSessionTools.ts +++ b/src/server/registerSessionTools.ts @@ -7,14 +7,14 @@ import { } from "../application/AnalysisContextQueries.js"; import { readAnalysisSnapshot } from "../application/binary/AnalysisSnapshotFiles.js"; import type { BinarySessionPort } from "../application/binary/BinarySession.js"; -import { createProcessCaptureEvidence } from "../application/ProcessEvidence.js"; -import { captureProcessScenario } from "../application/ProcessHarness.js"; +import { createProcessCaptureEvidence } from "../application/process/ProcessEvidence.js"; +import { captureProcessScenario } from "../process/capture/ProcessHarness.js"; import { toolContract } from "../contracts/toolContracts.js"; import type { AnalysisSnapshot } from "../domain/analysisSnapshot.js"; import { UnknownRegistryError } from "../domain/unknownRegistryError.js"; import { type AnalysisError } from "../domain/analysisErrorBase.js"; import type { Evidence } from "../domain/evidence.js"; -import type { ProcessCapture } from "../domain/processCapture.js"; +import type { ProcessCapture } from "../domain/process/processCapture.js"; import { ok, type Result } from "../domain/result.js"; import type { Logger } from "../logger.js"; import { mcpProgressReporter } from "./mcpProgress.js"; diff --git a/src/server/registerWebNetworkCaptureTool.ts b/src/server/registerWebNetworkCaptureTool.ts new file mode 100644 index 000000000..2c1a9e5c5 --- /dev/null +++ b/src/server/registerWebNetworkCaptureTool.ts @@ -0,0 +1,46 @@ +import type { + McpServer, + StandardSchemaWithJSON, +} from "@modelcontextprotocol/server"; +import type { WebNetworkCaptureService } from "../application/WebNetworkCaptureService.js"; +import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js"; +import { toolContract } from "../contracts/toolContracts.js"; +import type { Logger } from "../logger.js"; +import { logToolExecution } from "./toolLogging.js"; +import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; +import { toCallToolResult } from "./toolResult.js"; + +/** Bind historical inspection to its named contract and caller-owned Evidence writer. */ +export const registerWebNetworkCaptureTool = ( + server: McpServer, + service: WebNetworkCaptureService, + logger: Logger, + recordEvidence?: EvidenceWriter["recordEvidence"], +): void => { + const contract = toolContract("inspect_web_network_capture"); + const registration = toolRegistrationOptions(contract); + // Keep the exact advertised contract; the shared service validates raw input + // before effects so caller-declared sensitivity also covers invalid arguments. + // SDK pre-validation otherwise emits unprojected issue paths as plain text. + const inputSchema: StandardSchemaWithJSON = { + "~standard": { + ...registration.inputSchema["~standard"], + vendor: "rea-application", + validate: (value: unknown) => ({ value }), + }, + }; + server.registerTool( + contract.name, + { ...registration, inputSchema }, + async (input, context) => { + const result = await logToolExecution(logger, contract.name, () => + service.inspect(input, { signal: context.mcpReq.signal }), + ); + if (!result.ok) return toCallToolResult(result, contract); + const recorded = recordEvidence?.(result.value); + return recorded !== undefined && !recorded.ok + ? toCallToolResult(recorded, contract) + : toCallToolResult(result, contract); + }, + ); +}; diff --git a/src/server/sessionToolPolicies.ts b/src/server/sessionToolPolicies.ts index be151c59e..b1725b097 100644 --- a/src/server/sessionToolPolicies.ts +++ b/src/server/sessionToolPolicies.ts @@ -1,4 +1,4 @@ -export { PROCESS_PROVIDER } from "../application/ProcessEvidence.js"; +export { PROCESS_PROVIDER } from "../application/process/ProcessEvidence.js"; export { ARTIFACT_COMPARISON_PROVIDER, BUNDLE_COMPARISON_PROVIDER, diff --git a/tests/acceptance/applications/applicationEvidenceMcp.test.ts b/tests/acceptance/applications/applicationEvidenceMcp.test.ts index 3fdba32f4..6b21ac22c 100644 --- a/tests/acceptance/applications/applicationEvidenceMcp.test.ts +++ b/tests/acceptance/applications/applicationEvidenceMcp.test.ts @@ -1,9 +1,7 @@ -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import type { Client } from "@modelcontextprotocol/client"; import { describe, expect, it } from "vitest"; -import { createTestBinarySession } from "../../fixtures/binarySession.js"; import type { BinarySession } from "../../../src/application/binary/BinarySession.js"; -import { createServer } from "../../../src/server/createServer.js"; import { JAVASCRIPT_FEATURE_TRACE_FULL_EVIDENCE_EXAMPLE, JAVASCRIPT_FEATURE_TRACE_EXAMPLE, @@ -11,39 +9,10 @@ import { SOURCE_TO_BUNDLE_COMPARISON_EXAMPLE, } from "../../../src/contracts/javascript/javascriptApplicationWorkflowExamples.js"; import { createEvidence } from "../../../src/domain/evidence.js"; -import { observed } from "../../fixtures/analysisExecution.js"; - -interface TestHarness { - readonly client: Client; - readonly session: BinarySession; - readonly close: () => Promise; -} - -async function createTestHarness(): Promise { - const session = createTestBinarySession(() => ({ - execute: () => Promise.resolve(observed(null)), - close: () => Promise.resolve(), - })); - const server = createServer(session, session); - const client = new Client({ - name: "application-workflow-test", - version: "1", - }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - await server.connect(serverTransport); - await client.connect(clientTransport); - - return { - client, - session, - close: async () => { - await client.close(); - await server.close(); - await session.close(); - }, - }; -} +import { + createApplicationMcpHarness, + type ApplicationMcpHarness, +} from "./mcpHarness.js"; async function runInlineEvidenceScenarios( client: Client, @@ -103,7 +72,9 @@ async function runInlineEvidenceScenarios( expect(session.exportEvidenceBundle().records.length).toBeGreaterThan(2); } -async function runInlineWorkflowScenarios(harness: TestHarness): Promise { +async function runInlineWorkflowScenarios( + harness: ApplicationMcpHarness, +): Promise { const tracedInline = await harness.client.callTool({ name: "trace_application_feature", arguments: { @@ -250,7 +221,7 @@ async function assertRejectedInlineEvidence(client: Client): Promise { describe("application workflow MCP parity", () => { it("traces and compares authenticated graph Evidence in the session", async () => { - const harness = await createTestHarness(); + const harness = await createApplicationMcpHarness(); try { await runInlineEvidenceScenarios(harness.client, harness.session); await runInlineWorkflowScenarios(harness); diff --git a/tests/acceptance/applications/applicationExportShapeMcp.test.ts b/tests/acceptance/applications/applicationExportShapeMcp.test.ts index c104c8012..4fad71615 100644 --- a/tests/acceptance/applications/applicationExportShapeMcp.test.ts +++ b/tests/acceptance/applications/applicationExportShapeMcp.test.ts @@ -1,15 +1,14 @@ -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { describe, expect, it } from "vitest"; -import { copyFile, mkdir, rm } from "node:fs/promises"; +import { copyFile, mkdir, rm, writeFile } from "node:fs/promises"; import { join, relative, resolve } from "node:path"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; -import { createTestBinarySession } from "../../fixtures/binarySession.js"; -import { createServer } from "../../../src/server/createServer.js"; -import { observed } from "../../fixtures/analysisExecution.js"; import { analyzeJavaScriptApplication } from "../../../src/application/javascript/JavaScriptApplicationService.js"; +import { compareJavaScriptExportShapesEvidence } from "../../../src/application/javascript/JavaScriptApplicationWorkflowService.js"; import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js"; +import { javaScriptExportShapeComparisonResultSchema } from "../../../src/domain/javascript/javascriptExportShapeComparisonSchemas.js"; +import { createApplicationMcpHarness } from "./mcpHarness.js"; describe("application workflow MCP parity", () => { it("compares exact parser export shapes with inline Evidence", async () => { @@ -40,14 +39,8 @@ describe("application workflow MCP parity", () => { const leftAnalysis = javascriptApplicationAnalysisResultSchema.parse( left.value.normalized_result, ); - const session = createTestBinarySession(() => ({ - execute: () => Promise.resolve(observed(null)), - close: () => Promise.resolve(), - })); - const server = createServer(session, session); - const client = new Client({ name: "export-shape-test", version: "1" }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); + const harness = await createApplicationMcpHarness(); + const { client, session } = harness; const selectors = { left_module_path: "parser.mjs", left_export_name: "default", @@ -55,8 +48,6 @@ describe("application workflow MCP parity", () => { right_export_name: "default", }; try { - await server.connect(serverTransport); - await client.connect(clientTransport); const relativeApplication = await client.callTool({ name: "analyze_javascript_application", arguments: { input_path: relative(process.cwd(), leftRoot) }, @@ -121,10 +112,114 @@ describe("application workflow MCP parity", () => { }, }); } finally { - await client.close(); - await server.close(); - await session.close(); + await harness.close(); await rm(root, { recursive: true, force: true }); } }); }); + +describe("source-produced export comparison inventories", () => { + it("retains large source-produced export inventories through comparison schemas", async () => { + const fields = Array.from( + { length: 64 }, + (_, index) => `field${String(index)}: ${String(index)}`, + ).join(", "); + const returnSites = Array.from( + { length: 33 }, + (_, index) => + `if (value === ${String(index)}) return { type: "variant-${String(index)}", ${fields} };`, + ).join("\n"); + const extraExports = Array.from( + { length: 1_000 }, + (_, index) => + `export const candidate${String(index)} = ${String(index)};`, + ).join("\n"); + const inventorySource = `export default function parse(value) {\n${returnSites}\n throw new Error("no match");\n}\n${extraExports}`; + const pairedVariantsSource = `export default function parse(value) { +${returnSites} + throw new Error("no match"); +}`; + const [inventoryLeft, inventoryRight] = await Promise.all([ + analyzeSourceEvidence(inventorySource), + analyzeSourceEvidence(pairedVariantsSource), + ]); + const variants = compareEvidence(inventoryLeft, inventoryRight); + expect(variants.left).toMatchObject({ status: "selected" }); + expect(variants.coverage).toMatchObject({ + paired_variants: 33, + unpaired_left_variants: 0, + unpaired_right_variants: 0, + left_source_omitted_variants: 0, + right_source_omitted_variants: 0, + left_omitted_fields: 0, + right_omitted_fields: 0, + }); + + const missing = compareEvidence(inventoryLeft, inventoryRight, { + leftExportName: "missing", + }); + expect(missing.left).toMatchObject({ + status: "missing", + omitted_candidates: 0, + }); + expect(missing.left.candidates).toHaveLength(1_001); + + const changedFieldsLeft = Array.from( + { length: 10_001 }, + (_, index) => `field${String(index)}: ${String(index)}`, + ).join(", "); + const changedFieldsRight = Array.from( + { length: 10_001 }, + (_, index) => `field${String(index)}: ${String(index + 1)}`, + ).join(", "); + const [changeLeft, changeRight] = await Promise.all([ + analyzeSourceEvidence( + `export default () => ({ type: "record", ${changedFieldsLeft} });`, + ), + analyzeSourceEvidence( + `export default () => ({ type: "record", ${changedFieldsRight} });`, + ), + ]); + const changes = compareEvidence(changeLeft, changeRight); + expect(changes.changes).toHaveLength(10_001); + expect(changes.summary).toEqual({ + added: 0, + removed: 0, + changed: 10_001, + unknown: 0, + }); + expect(changes.coverage).toMatchObject({ + status: "complete-within-inputs", + omitted_changes: 0, + left_omitted_fields: 0, + right_omitted_fields: 0, + }); + }, 30_000); +}); + +const analyzeSourceEvidence = async (source: string) => { + const root = await createTestTempDirectory("rea-export-shape-inventory-"); + await writeFile(join(root, "parser.mjs"), source); + const analyzed = await analyzeJavaScriptApplication({ input_path: root }); + if (!analyzed.ok) throw analyzed.error; + return analyzed.value; +}; + +const compareEvidence = ( + left: Awaited>, + right: Awaited>, + selectors: { readonly leftExportName?: string } = {}, +) => { + const compared = compareJavaScriptExportShapesEvidence({ + left, + right, + left_module_path: "parser.mjs", + left_export_name: selectors.leftExportName ?? "default", + right_module_path: "parser.mjs", + right_export_name: "default", + }); + if (!compared.ok) throw compared.error; + return javaScriptExportShapeComparisonResultSchema.parse( + compared.value.normalized_result, + ); +}; diff --git a/tests/acceptance/applications/mcpHarness.ts b/tests/acceptance/applications/mcpHarness.ts new file mode 100644 index 000000000..5f971e477 --- /dev/null +++ b/tests/acceptance/applications/mcpHarness.ts @@ -0,0 +1,39 @@ +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; + +import { createTestBinarySession } from "../../fixtures/binarySession.js"; +import type { BinarySession } from "../../../src/application/binary/BinarySession.js"; +import { observed } from "../../fixtures/analysisExecution.js"; +import { createServer } from "../../../src/server/createServer.js"; + +/** Owned MCP SDK session shared by application workflow scenarios. */ +export interface ApplicationMcpHarness { + readonly client: Client; + readonly session: BinarySession; + readonly close: () => Promise; +} + +/** Connect application workflows through the production MCP registration. */ +export async function createApplicationMcpHarness(): Promise { + const session = createTestBinarySession(() => ({ + execute: () => Promise.resolve(observed(null)), + close: () => Promise.resolve(), + })); + const server = createServer(session, session); + const client = new Client({ + name: "application-workflow-test", + version: "1", + }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await server.connect(serverTransport); + await client.connect(clientTransport); + + return { + client, + session, + close: async () => { + await Promise.all([client.close(), server.close(), session.close()]); + }, + }; +} diff --git a/tests/acceptance/applications/runtime.test.ts b/tests/acceptance/applications/runtime.test.ts index 49bf0975f..d7aff2937 100644 --- a/tests/acceptance/applications/runtime.test.ts +++ b/tests/acceptance/applications/runtime.test.ts @@ -17,12 +17,13 @@ const fixturePath = fileURLToPath( new URL("../../fixtures/fakeLauncher.mjs", import.meta.url), ); -const expectAvailableToolInventory = async (client: Client): Promise => { +const expectAvailableToolInventory = async (client: Client) => { const listed = await client.listTools(); const status = await client.callTool({ name: "binary_session", arguments: {}, }); + expect(status.isError).not.toBe(true); const availability = z .object({ result: z.object({ @@ -43,6 +44,7 @@ const expectAvailableToolInventory = async (client: Client): Promise => { expect(new Set(listed.tools.map(({ name }) => name))).toEqual( new Set(TOOL_CONTRACTS.map(({ name }) => name)), ); + return status; }; /** @@ -57,72 +59,83 @@ const completeStderrRecords = (stderr: string): unknown[] => .filter((line) => line.length > 0) .map((line): unknown => JSON.parse(line)); +const callCurrentDocument = async (target?: { + readonly path: string; + readonly kind?: "database"; +}) => { + const transport = new StdioClientTransport({ + command: process.execPath, + args: [mainPath], + cwd: process.cwd(), + env: { + PATH: process.env.PATH ?? "", + HOPPER_LAUNCHER_PATH: process.execPath, + ...(target === undefined + ? {} + : { + HOPPER_TARGET_PATH: target.path, + ...(target.kind === undefined + ? {} + : { HOPPER_TARGET_KIND: target.kind }), + }), + HOPPER_LOADER_ARGS_JSON: JSON.stringify([fixturePath]), + }, + stderr: "pipe", + }); + let stderr = ""; + transport.stderr?.on("data", (chunk: Buffer | string) => { + stderr += chunk.toString(); + }); + const client = new Client({ name: "runtime-smoke", version: "1.0.0" }); + + let result: Awaited>; + try { + await client.connect(transport); + result = await client.callTool({ + name: "current_document", + arguments: {}, + }); + } finally { + await client.close(); + await transport.close(); + } + return { result, stderr, records: completeStderrRecords(stderr) }; +}; + describe("production stdio runtime", () => { // The `test:acceptance` lane runs the real entrypoint on the current host; // these cases state each supported host's result in the test name and body. - const callCurrentDocument = async () => { - const transport = new StdioClientTransport({ - command: process.execPath, - args: [mainPath], - cwd: process.cwd(), - env: { - PATH: process.env.PATH ?? "", - HOPPER_LAUNCHER_PATH: process.execPath, - HOPPER_TARGET_PATH: process.execPath, - HOPPER_LOADER_ARGS_JSON: JSON.stringify([fixturePath]), - }, - stderr: "pipe", - }); - let stderr = ""; - transport.stderr?.on("data", (chunk: Buffer | string) => { - stderr += chunk.toString(); - }); - const client = new Client({ name: "runtime-smoke", version: "1.0.0" }); - let result: Awaited>; - try { - await client.connect(transport); - await expectAvailableToolInventory(client); - result = await client.callTool({ - name: "current_document", - arguments: {}, - }); - } finally { - await client.close(); - await transport.close(); - } - return { result, stderr, records: completeStderrRecords(stderr) }; - }; - - it.runIf(process.platform === "linux")( - "reports the Linux current-document failure through MCP and structured logs", - async () => { - const { result, stderr, records } = await callCurrentDocument(); - expect(result.isError).toBe(true); - expect(result.content).toEqual( - expect.arrayContaining([ - expect.objectContaining({ type: "text", text: expect.any(String) }), - ]), - ); - expect(records).toContainEqual( + it("reports a missing target through MCP and structured logs", async () => { + const { result, stderr, records } = await callCurrentDocument(); + expect(result.isError).toBe(true); + expect(result.content).toEqual( + expect.arrayContaining([ expect.objectContaining({ - application: "rea", - mode: "mcp", - layer: "server", - tool: "current_document", - status: "error", + type: "text", + text: expect.stringContaining("No app is open."), }), - ); - expect(stderr).not.toContain("HOPPER_LOADER_ARGS_JSON"); - expect(stderr).not.toContain(fixturePath); - }, - 15_000, - ); + ]), + ); + expect(records).toContainEqual( + expect.objectContaining({ + application: "rea", + mode: "mcp", + layer: "server", + tool: "current_document", + status: "error", + }), + ); + expect(stderr).not.toContain("HOPPER_LOADER_ARGS_JSON"); + expect(stderr).not.toContain(fixturePath); + }, 15_000); it.runIf(process.platform === "darwin")( "returns the selected document on macOS through MCP and structured logs", async () => { - const { result, stderr, records } = await callCurrentDocument(); + const { result, stderr, records } = await callCurrentDocument({ + path: process.execPath, + }); expect(result.isError).not.toBe(true); const observation = evidenceResultOf(z.literal("fixture")).parse( result.structuredContent, @@ -146,17 +159,13 @@ describe("production stdio runtime", () => { 15_000, ); - it("starts with a database-kind initial target without a fatal record", async () => { + it("serves the compiled catalog without an initial target or fatal record", async () => { const transport = new StdioClientTransport({ command: process.execPath, args: [mainPath], cwd: process.cwd(), env: { PATH: process.env.PATH ?? "", - HOPPER_LAUNCHER_PATH: process.execPath, - HOPPER_TARGET_PATH: new URL(import.meta.url).pathname, - HOPPER_TARGET_KIND: "database", - HOPPER_LOADER_ARGS_JSON: JSON.stringify([fixturePath]), }, stderr: "pipe", }); @@ -164,23 +173,24 @@ describe("production stdio runtime", () => { transport.stderr?.on("data", (chunk: Buffer | string) => { stderr += chunk.toString(); }); - const client = new Client({ name: "database-runtime", version: "1.0.0" }); + const client = new Client({ name: "catalog-runtime", version: "1.0.0" }); try { await client.connect(transport); - // The catalog itself is already proven against the compiled entrypoint - // above, so this test asserts the part that is unique to a database-kind - // initial target: the server comes up and serves tools without a fatal - // startup record. - const listed = await client.listTools(); - expect(listed.tools.length).toBeGreaterThan(0); - // A database-kind initial target must also serve a tool. Merely listing - // the catalog starts no bridge work, so without this call the child - // writes no startup records at all and the fatal-record assertion below - // would pass without observing anything. - await expect( - client.callTool({ name: "current_document", arguments: {} }), - ).resolves.toBeDefined(); + // Keep catalog parity independent of initial target/provider startup. + const status = await expectAvailableToolInventory(client); + expect( + z + .object({ result: z.object({ open: z.boolean() }) }) + .parse(status.structuredContent).result, + ).toEqual({ open: false }); + // Closing the empty session produces a structured lifecycle record, + // making the fatal-record check observable without launching a provider. + const closed = await client.callTool({ + name: "close_binary", + arguments: {}, + }); + expect(closed.isError).not.toBe(true); } finally { await client.close(); await transport.close(); @@ -194,6 +204,9 @@ describe("production stdio runtime", () => { records.length, "no startup records were observed, so the fatal-record check is vacuous", ).toBeGreaterThan(0); + expect(records).toContainEqual( + expect.objectContaining({ tool: "close_binary", status: "ok" }), + ); const fatal = records.filter( (record): record is { level: number } => typeof record === "object" && @@ -204,4 +217,30 @@ describe("production stdio runtime", () => { ); expect(fatal).toEqual([]); }, 15_000); + + it.runIf(process.platform === "darwin")( + "serves a database-kind initial target without a fatal record on macOS", + async () => { + const { result, records } = await callCurrentDocument({ + path: fileURLToPath(import.meta.url), + kind: "database", + }); + expect(result.isError).not.toBe(true); + evidenceResultOf(z.literal("fixture")).parse(result.structuredContent); + expect(records).toContainEqual( + expect.objectContaining({ tool: "current_document", status: "ok" }), + ); + expect( + records.some( + (record) => + typeof record === "object" && + record !== null && + "level" in record && + typeof record.level === "number" && + record.level >= 50, + ), + ).toBe(false); + }, + 15_000, + ); }); diff --git a/tests/acceptance/nonBinaryComposition.test.ts b/tests/acceptance/nonBinaryComposition.test.ts index a5229ebf0..f7c28fa41 100644 --- a/tests/acceptance/nonBinaryComposition.test.ts +++ b/tests/acceptance/nonBinaryComposition.test.ts @@ -89,6 +89,24 @@ cliTest.skipIf(process.platform !== "linux")( }, ); +cliTest.skipIf(process.platform !== "linux")( + "resolves relative firmware CLI paths against the operator working directory", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-firmware-cli-relative-"); + await writeFile(join(root, "input.bin"), "firmware fixture"); + const cliResult = await cli.run({ + arguments: ["inspect-firmware-regions", "input.bin", "--json"], + cwd: root, + environment: { REA_LOG_LEVEL: "silent" }, + timeoutMs: 10_000, + }); + // A relative path must pass input validation and reach the provider, + // which reports the missing firmware engine — not a validation error. + expect(cliResult.exitCode).toBe(1); + expect(cliResult.json).toMatchObject({ code: "capability_unavailable" }); + }, +); + cliTest( "preserves optional JavaScript recovery configuration through CLI and MCP", async ({ cli }) => { diff --git a/tests/boundary/browser/browserObservationService.test.ts b/tests/boundary/browser/browserObservationService.test.ts deleted file mode 100644 index 9dd1c6328..000000000 --- a/tests/boundary/browser/browserObservationService.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { listBrowserTargetsInputSchema } from "../../../src/domain/browserObservation.js"; -import { browserCaptureComparisonInputSchema } from "../../../src/domain/browserCaptureComparison.js"; -import { compareWebScreenshotsInputSchema } from "../../../src/domain/webScreenshot.js"; -import { BROWSER_TOOL_CONTRACTS } from "../../../src/contracts/browserToolContracts.js"; -import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; -import { - compareWebCaptureEvidence, - compareWebScreenshotEvidence, - listBrowserTargets, -} from "../../../src/application/BrowserObservationService.js"; - -describe("browser observation service prerequisites", () => { - it("reports a missing provider without requiring a permission grant", async () => { - const endpoint = "http://127.0.0.1:9222"; - const origin = "http://[::1]:3000"; - const result = await listBrowserTargets( - undefined, - listBrowserTargetsInputSchema.parse({ - cdp_endpoint: endpoint, - allowed_origins: [origin], - }), - ); - expect(result).toMatchObject({ - ok: false, - error: { - _tag: "AnalysisCapabilityUnavailableError", - }, - }); - }); - it("compares supplied scenarios and PNG artifacts without a CDP endpoint", async () => { - const browser = new CdpBrowserProvider(); - const captures = await compareWebCaptureEvidence( - browser, - browserCaptureComparisonInputSchema.parse( - example("compare_web_captures"), - ), - ); - const screenshots = await compareWebScreenshotEvidence( - browser, - compareWebScreenshotsInputSchema.parse( - example("compare_web_screenshots"), - ), - ); - for (const result of [captures, screenshots]) { - expect(result.ok).toBe(true); - if (!result.ok) throw result.error; - expect(result.value.parameters).not.toHaveProperty("cdp_endpoint"); - } - }); -}); - -const example = (name: string) => { - const input = BROWSER_TOOL_CONTRACTS.find((tool) => tool.name === name) - ?.examples[0]?.input; - if (input === undefined) throw new Error(`Missing example for ${name}`); - return input; -}; diff --git a/tests/boundary/browser/cdpBrowserProvider.discovery_authorization.test.ts b/tests/boundary/browser/cdpBrowserProvider.discovery_authorization.test.ts index d65e150b4..053cd673e 100644 --- a/tests/boundary/browser/cdpBrowserProvider.discovery_authorization.test.ts +++ b/tests/boundary/browser/cdpBrowserProvider.discovery_authorization.test.ts @@ -1,4 +1,4 @@ -import { expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { @@ -6,9 +6,9 @@ import { listBrowserTargetsInputSchema, } from "../../../src/domain/browserObservation.js"; import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; -import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js"; +import { trackBrowser } from "./cdpBrowserProvider.support.js"; -describeBrowser("CdpBrowserProvider: discovery authorization 1", () => { +describe("CdpBrowserProvider: discovery authorization 1", () => { it("discovers pages and captures a selected target without an origin list", async () => { const browser = await startFakeCdpBrowser(); trackBrowser(browser); @@ -73,9 +73,12 @@ describeBrowser("CdpBrowserProvider: discovery authorization 1", () => { const browser = await startFakeCdpBrowser({ urlShapedAllowedTitle }); trackBrowser(browser); const provider = new CdpBrowserProvider(); - const input = { + const listInput = { cdp_endpoint: browser.endpoint, allowed_origins: [browser.allowedOrigin], + }; + const input = { + ...listInput, target_id: "allowed-page", }; const authority = browser.allowedOrigin.replace(/^https?:\/\//u, ""); @@ -86,7 +89,7 @@ describeBrowser("CdpBrowserProvider: discovery authorization 1", () => { ? "/app?startup=title-secret#fragment" : `${prefix}${authority}/app?startup=title-secret#fragment`; const listed = await provider.listTargets( - listBrowserTargetsInputSchema.parse(input), + listBrowserTargetsInputSchema.parse(listInput), ); if (!listed.ok) throw listed.error; expect(listed.value.targets[0]?.title).toBe(expected); @@ -155,7 +158,7 @@ describeBrowser("CdpBrowserProvider: discovery authorization 1", () => { }); }); -describeBrowser("CdpBrowserProvider: discovery authorization 2", () => { +describe("CdpBrowserProvider: discovery authorization 2", () => { it("rejects empty browser attachment session identifiers", async () => { const browser = await startFakeCdpBrowser({ invalidAttachedSession: true }); trackBrowser(browser); diff --git a/tests/boundary/browser/cdpBrowserProvider.document_script.test.ts b/tests/boundary/browser/cdpBrowserProvider.document_script.test.ts index e770000b1..691186049 100644 --- a/tests/boundary/browser/cdpBrowserProvider.document_script.test.ts +++ b/tests/boundary/browser/cdpBrowserProvider.document_script.test.ts @@ -1,4 +1,4 @@ -import { expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js"; @@ -6,20 +6,9 @@ import { analyzeWebBundleInputSchema } from "../../../src/domain/webBundleAnalys import { discoverWebMcpToolsInputSchema } from "../../../src/domain/webMcpDiscovery.js"; import { captureWebScreenshotInputSchema } from "../../../src/domain/webScreenshot.js"; import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; -import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js"; - -describeBrowser("CdpBrowserProvider: document script 1", () => { - it("rejects unknown WebMCP discovery fields", () => { - expect( - discoverWebMcpToolsInputSchema.safeParse({ - cdp_endpoint: "http://127.0.0.1:9222", - allowed_origins: ["https://example.test"], - target_id: "target", - unknown_field: true, - }).success, - ).toBe(false); - }); +import { trackBrowser } from "./cdpBrowserProvider.support.js"; +describe("CdpBrowserProvider: approved accessibility and source capture", () => { it("captures approved accessibility text inline", async () => { const browser = await startFakeCdpBrowser(); trackBrowser(browser); @@ -120,7 +109,7 @@ describeBrowser("CdpBrowserProvider: document script 1", () => { }); }); -describeBrowser("CdpBrowserProvider: transient WebMCP frames", () => { +describe("CdpBrowserProvider: transient WebMCP frames", () => { it("drops stale child tools and reports partial coverage when a blank commit remains", async () => { const browser = await startFakeCdpBrowser({ webMcpTools: true, @@ -205,7 +194,7 @@ describeBrowser("CdpBrowserProvider: transient WebMCP frames", () => { }); }); -describeBrowser("CdpBrowserProvider: document script 2", () => { +describe("CdpBrowserProvider: WebMCP discovery and completeness", () => { it("discovers untrusted WebMCP declarations without registering or invoking them", async () => { const browser = await startFakeCdpBrowser({ webMcpTools: true }); trackBrowser(browser); @@ -360,7 +349,7 @@ describeBrowser("CdpBrowserProvider: document script 2", () => { }); }); -describeBrowser("CdpBrowserProvider WebMCP inventory completeness", () => { +describe("CdpBrowserProvider WebMCP inventory completeness", () => { it("retains every field in a large declared WebMCP input schema", async () => { const propertyCount = 5_001; const browser = await startFakeCdpBrowser({ @@ -392,7 +381,7 @@ describeBrowser("CdpBrowserProvider WebMCP inventory completeness", () => { }); }); -describeBrowser("CdpBrowserProvider: document script 3", () => { +describe("CdpBrowserProvider: frame scope and screenshots", () => { it("removes WebMCP declarations after their child frame leaves scope", async () => { const browser = await startFakeCdpBrowser({ webMcpTools: true, @@ -474,7 +463,7 @@ describeBrowser("CdpBrowserProvider: document script 3", () => { }); }); -describeBrowser("CdpBrowserProvider: document script 4", () => { +describe("CdpBrowserProvider: complete passive inventories", () => { it("returns every frame, resource, worker, accessibility, and storage inventory item", async () => { const browser = await startFakeCdpBrowser({ extraCollections: true }); trackBrowser(browser); @@ -501,7 +490,7 @@ describeBrowser("CdpBrowserProvider: document script 4", () => { }); }); -describeBrowser("CdpBrowserProvider: WebMCP registration owners", () => { +describe("CdpBrowserProvider: WebMCP registration owners", () => { it.each(["retain", "remove-second"] as const)( "keeps same-URL frame owners separate: %s", async (mode) => { diff --git a/tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts b/tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts index d559b9088..3d5fc7a6f 100644 --- a/tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts +++ b/tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts @@ -1,4 +1,4 @@ -import { expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { @@ -11,9 +11,9 @@ import { startFakeCdpBrowser, type FakeCdpBrowser, } from "../../fixtures/fakeCdpBrowser.js"; -import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js"; +import { trackBrowser } from "./cdpBrowserProvider.support.js"; -describeBrowser("CdpBrowserProvider: lifecycle failures 1", () => { +describe("CdpBrowserProvider: cancellation and session events", () => { it("projects direct-session cancellation onto the requested operation", async () => { const browser = await startFakeCdpBrowser({ pageScopedVersionWebSocket: true, @@ -132,7 +132,7 @@ describeBrowser("CdpBrowserProvider: lifecycle failures 1", () => { }); }); -describeBrowser("CdpBrowserProvider: lifecycle failures 2", () => { +describe("CdpBrowserProvider: session termination and scope changes", () => { it("ends a browser session when its flat target session detaches", async () => { const browser = await startFakeCdpBrowser({ sessionTimeline: "target_detached", @@ -243,7 +243,7 @@ describeBrowser("CdpBrowserProvider: lifecycle failures 2", () => { }); }); -describeBrowser("CdpBrowserProvider: lifecycle failures 3", () => { +describe("CdpBrowserProvider: protocol failures and cancellation cleanup", () => { it("degrades optional domains but propagates protocol and payload failures", async () => { const optional = await startFakeCdpBrowser({ unsupportedMethods: ["Accessibility.getFullAXTree"], @@ -373,7 +373,7 @@ describeBrowser("CdpBrowserProvider: lifecycle failures 3", () => { }); }); -describeBrowser("CdpBrowserProvider: lifecycle failures 4", () => { +describe("CdpBrowserProvider: navigation commit and target identity", () => { it("waits for navigation commit and rechecks the attached main-frame origin", async () => { const transitioning = await startFakeCdpBrowser({ transitionalFrameReads: 2, diff --git a/tests/boundary/browser/cdpBrowserProvider.network.test.ts b/tests/boundary/browser/cdpBrowserProvider.network.test.ts index 4787435d3..152b2ef14 100644 --- a/tests/boundary/browser/cdpBrowserProvider.network.test.ts +++ b/tests/boundary/browser/cdpBrowserProvider.network.test.ts @@ -1,11 +1,64 @@ -import { expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js"; import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; -import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js"; +import { trackBrowser } from "./cdpBrowserProvider.support.js"; + +describe("CdpBrowserProvider: network 1", () => { + it("retains same-origin redirect hops on the one final request", async () => { + const browser = await startFakeCdpBrowser({ redirectWithinOrigin: true }); + trackBrowser(browser); + const result = await new CdpBrowserProvider().inspectPage( + inspectWebPageInputSchema.parse({ + cdp_endpoint: browser.endpoint, + allowed_origins: [browser.allowedOrigin], + target_id: "allowed-page", + observation_ms: 0, + }), + ); + + if (!result.ok) throw result.error; + expect(result.value.network.requests).toHaveLength(1); + expect(result.value.network.requests[0]).toMatchObject({ + request_id: "request-1", + url: `${browser.allowedOrigin}/redirected?token=final-secret`, + status: 200, + redirects: [ + { + url: `${browser.allowedOrigin}/api?token=network-secret`, + response_url: `${browser.allowedOrigin}/api?token=network-secret`, + method: "POST", + resource_type: "Fetch", + status: 301, + mime_type: "text/plain", + encoded_data_length: 23, + request_timestamp: 7, + redirect_event_timestamp: 8, + }, + { + url: `${browser.allowedOrigin}/intermediate`, + response_url: `${browser.allowedOrigin}/intermediate`, + method: "GET", + resource_type: "Fetch", + status: 302, + mime_type: "text/plain", + encoded_data_length: 27, + request_timestamp: 8, + redirect_event_timestamp: 9, + }, + ], + }); + expect(JSON.stringify(result.value.network.requests)).not.toContain( + "redirect-header-secret", + ); + expect(result.value.completeness.excluded).not.toContainEqual({ + section: "network_requests", + reason: "invalid_protocol_value", + count: expect.any(Number), + }); + }); -describeBrowser("CdpBrowserProvider: network 1", () => { it("drops network evidence when a request redirects outside the approved origin", async () => { const browser = await startFakeCdpBrowser({ redirectToDisallowedOrigin: true, @@ -23,4 +76,42 @@ describeBrowser("CdpBrowserProvider: network 1", () => { if (!result.ok) throw result.error; expect(result.value.network.requests).toEqual([]); }); + + it("does not retain excluded prior-hop data when a redirect returns in scope", async () => { + const browser = await startFakeCdpBrowser({ + redirectFromDisallowedOrigin: true, + }); + trackBrowser(browser); + const result = await new CdpBrowserProvider().inspectPage( + inspectWebPageInputSchema.parse({ + cdp_endpoint: browser.endpoint, + allowed_origins: [browser.allowedOrigin], + target_id: "allowed-page", + observation_ms: 0, + }), + ); + + if (!result.ok) throw result.error; + expect(result.value.network.requests).toHaveLength(1); + expect(result.value.network.requests[0]).toMatchObject({ + url: `${browser.allowedOrigin}/returned`, + redirects: [], + }); + const output = JSON.stringify(result.value.network.requests); + expect(output).not.toContain("private.example.test"); + expect(output).not.toContain("redirect-header-secret"); + expect(output).not.toContain("redirect-body-secret"); + expect(result.value.completeness.policy_filtered_sections).toContain( + "network_requests", + ); + expect(result.value.completeness.status).toBe("policy_filtered"); + expect(result.value.completeness.unavailable_sections).not.toContain( + "network_requests", + ); + expect(result.value.completeness.excluded).not.toContainEqual({ + section: "network_requests", + reason: "invalid_protocol_value", + count: expect.any(Number), + }); + }); }); diff --git a/tests/boundary/browser/cdpBrowserProvider.redirectErrors.test.ts b/tests/boundary/browser/cdpBrowserProvider.redirectErrors.test.ts new file mode 100644 index 000000000..acfbe4b07 --- /dev/null +++ b/tests/boundary/browser/cdpBrowserProvider.redirectErrors.test.ts @@ -0,0 +1,150 @@ +import { describe, expect, it } from "vitest"; + +import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; +import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js"; +import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; +import type { FakeOptions } from "../../fixtures/fakeCdpBrowserTypes.js"; +import { trackBrowser } from "./cdpBrowserProvider.support.js"; + +const inspectNetwork = async (options: FakeOptions) => { + const browser = await startFakeCdpBrowser(options); + trackBrowser(browser); + const result = await new CdpBrowserProvider().inspectPage( + inspectWebPageInputSchema.parse({ + cdp_endpoint: browser.endpoint, + allowed_origins: [browser.allowedOrigin], + target_id: "allowed-page", + observation_ms: 0, + include_json_body_shapes: true, + }), + ); + if (!result.ok) throw result.error; + return { browser, inspection: result.value }; +}; + +describe("CdpBrowserProvider: redirect errors", () => { + it.each([ + { label: "missing", url: undefined, reason: "invalid_protocol_value" }, + { label: "malformed", url: "http://%", reason: "invalid_protocol_value" }, + { + label: "unsupported scheme", + url: "file:///private/redirect", + reason: "unsupported_url", + }, + ])("classifies $label redirect response URLs", async ({ url, reason }) => { + const { inspection } = await inspectNetwork({ + malformedRedirectResponse: true, + ...(url === undefined ? {} : { redirectResponseUrl: url }), + }); + + expect(inspection.network.requests).toEqual([]); + expect(inspection.completeness.excluded).toContainEqual({ + section: "network_requests", + reason, + count: expect.any(Number), + }); + const section = + reason === "unsupported_url" + ? inspection.completeness.policy_filtered_sections + : inspection.completeness.unavailable_sections; + expect(section).toContain("network_requests"); + }); + + it.each([ + { label: "null", envelope: null }, + { label: "array", envelope: [] }, + { label: "scalar", envelope: "malformed redirect" }, + ])( + "preserves prior request for a $label redirect envelope", + async ({ envelope }) => { + const { browser, inspection } = await inspectNetwork({ + malformedRedirectResponse: true, + redirectResponseEnvelope: envelope, + }); + + expect(inspection.network.requests).toHaveLength(1); + expect(inspection.network.requests[0]).toMatchObject({ + request_id: "request-1", + url: `${browser.allowedOrigin}/malformed-redirect-prior`, + status: 201, + mime_type: "application/json", + encoded_data_length: null, + redirects: [ + { + url: `${browser.allowedOrigin}/api?token=network-secret`, + response_url: `${browser.allowedOrigin}/api?token=network-secret`, + request_timestamp: 7, + redirect_event_timestamp: 8, + }, + ], + }); + expect( + inspection.network.requests[0]?.body_shapes.request, + ).not.toBeNull(); + expect(inspection.network.requests[0]?.body_shapes.response).toBeNull(); + expect(inspection.network.requests[0]?.body_shapes.status).toBe( + "partial", + ); + expect(inspection.metadata.responses).toHaveLength(1); + expect(inspection.metadata.responses[0]).toMatchObject({ + url: `${browser.allowedOrigin}/malformed-redirect-prior`, + mime_type: "application/json", + content_length: 123, + content_encoding: "gzip", + }); + expect(inspection.completeness.unavailable_sections).toContain( + "network_requests", + ); + expect(inspection.completeness.unavailable_sections).toContain( + "json_body_shapes", + ); + expect(inspection.completeness.excluded).toContainEqual({ + section: "network_requests", + reason: "invalid_protocol_value", + count: expect.any(Number), + }); + expect(inspection.completeness.excluded).toContainEqual({ + section: "json_body_shapes", + reason: "invalid_protocol_value", + count: expect.any(Number), + }); + expect(JSON.stringify(inspection.network.requests)).not.toContain( + "malformed-redirect-final", + ); + }, + ); + + it("keeps authorized prior evidence when the later response URL is disallowed", async () => { + const { browser, inspection } = await inspectNetwork({ + malformedRedirectResponse: true, + redirectResponseEnvelope: null, + responseAfterMalformedUrl: + "https://private.example.test/late?token=unauthorized-secret", + }); + + expect(inspection.network.requests).toHaveLength(1); + expect(inspection.network.requests[0]).toMatchObject({ + url: `${browser.allowedOrigin}/malformed-redirect-prior`, + status: 201, + mime_type: "application/json", + encoded_data_length: null, + body_shapes: { status: "partial", response: null }, + }); + expect(inspection.metadata.responses).toHaveLength(1); + expect(inspection.metadata.responses[0]?.url).toBe( + `${browser.allowedOrigin}/malformed-redirect-prior`, + ); + expect(inspection.completeness.unavailable_sections).toContain( + "json_body_shapes", + ); + expect(inspection.completeness.excluded).toContainEqual({ + section: "network_requests", + reason: "invalid_protocol_value", + count: expect.any(Number), + }); + const output = JSON.stringify(inspection); + expect(output).not.toContain("private.example.test"); + expect(output).not.toContain("unauthorized-secret"); + expect(output).not.toContain("header-secret"); + }); +}); diff --git a/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts b/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts index 20bcabaac..d47764381 100644 --- a/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts +++ b/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts @@ -1,4 +1,4 @@ -import { expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { @@ -9,9 +9,9 @@ import { startFakeCdpBrowser, type FakeCdpBrowser, } from "../../fixtures/fakeCdpBrowser.js"; -import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js"; +import { trackBrowser } from "./cdpBrowserProvider.support.js"; -describeBrowser("CdpBrowserProvider: sensitive data 1", () => { +describe("CdpBrowserProvider: passive evidence redaction", () => { it("captures bounded passive evidence without retaining sensitive values", async () => { const browser = await startFakeCdpBrowser({ binaryWebSocketEvent: true, @@ -158,7 +158,7 @@ describeBrowser("CdpBrowserProvider: sensitive data 1", () => { }); }); -describeBrowser("CdpBrowserProvider: page cookie scope", () => { +describe("CdpBrowserProvider: page cookie scope", () => { it("uses the attached page URL for cookies and its origin for DOM storage", async () => { const browser = await startFakeCdpBrowser(); trackBrowser(browser); @@ -190,7 +190,7 @@ describeBrowser("CdpBrowserProvider: page cookie scope", () => { }); }); -describeBrowser("CdpBrowserProvider: complete storage fingerprints", () => { +describe("CdpBrowserProvider: complete storage fingerprints", () => { it("fingerprints cache bodies above the former byte ceiling", async () => { const body = "x".repeat(64 * 1_024 + 1); const browser = await startFakeCdpBrowser({ cachedResponseBody: body }); @@ -217,7 +217,7 @@ describeBrowser("CdpBrowserProvider: complete storage fingerprints", () => { }); }); -describeBrowser("CdpBrowserProvider: sensitive data 2", () => { +describe("CdpBrowserProvider: explicitly requested text and payload shapes", () => { it("captures requested console text verbatim and value-free payload shapes", async () => { const browser = await startFakeCdpBrowser({ sensitiveShapes: true, @@ -309,45 +309,7 @@ describeBrowser("CdpBrowserProvider: sensitive data 2", () => { }); }); -describeBrowser("CdpBrowserProvider: complete sensitive data", () => { - it("returns complete requested text and payload shapes verbatim", async () => { - const browser = await startFakeCdpBrowser({ sensitiveShapes: true }); - trackBrowser(browser); - const request = inspectWebPageInputSchema.parse({ - cdp_endpoint: browser.endpoint, - allowed_origins: [browser.allowedOrigin], - target_id: "allowed-page", - observation_ms: 0, - include_console_text: true, - include_json_body_shapes: true, - include_websocket_shapes: true, - }); - const result = await new CdpBrowserProvider().inspectPage(request); - - if (!result.ok) throw result.error; - expect(result.value.console.events[0]?.text_capture).toEqual({ - status: "included", - values: [ - { - argument_index: 0, - type: "string", - text: "authorization=Bearer console-secret", - }, - { argument_index: 1, type: "number", text: "42" }, - ], - retained_bytes: - Buffer.byteLength("authorization=Bearer console-secret") + - Buffer.byteLength("42"), - }); - expect(result.value.network.requests[0]?.body_shapes.status).toBe( - "included", - ); - expect(result.value.network.websocket_events[0]?.payload_shape).toEqual({ - format: "json", - json_shape: expect.objectContaining({ root_type: "object" }), - }); - }); - +describe("CdpBrowserProvider: malformed approved payloads", () => { it("fails closed on malformed approved response and binary payload encodings", async () => { const browser = await startFakeCdpBrowser({ invalidResponseBodyBase64: true, diff --git a/tests/boundary/browser/cdpBrowserProvider.support.ts b/tests/boundary/browser/cdpBrowserProvider.support.ts index 1afe9e074..8e59d2d03 100644 --- a/tests/boundary/browser/cdpBrowserProvider.support.ts +++ b/tests/boundary/browser/cdpBrowserProvider.support.ts @@ -1,4 +1,4 @@ -import { afterEach, describe } from "vitest"; +import { afterEach } from "vitest"; import type { FakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; @@ -11,5 +11,3 @@ afterEach(async () => { export const trackBrowser = (browser: FakeCdpBrowser): void => { browsers.push(browser); }; - -export const describeBrowser = describe; diff --git a/tests/boundary/browser/cdpBrowserProvider.timelineEvents.test.ts b/tests/boundary/browser/cdpBrowserProvider.timelineEvents.test.ts index c77af5985..e9f1f9a60 100644 --- a/tests/boundary/browser/cdpBrowserProvider.timelineEvents.test.ts +++ b/tests/boundary/browser/cdpBrowserProvider.timelineEvents.test.ts @@ -1,29 +1,11 @@ -import { expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { observeWebSessionInputSchema } from "../../../src/domain/browserSession.js"; import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; -import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js"; - -describeBrowser("CdpBrowserProvider navigation timeline", () => { - it("accepts caller-selected windows and rejects the removed event-count option", () => { - const accepted = observeWebSessionInputSchema.safeParse({ - cdp_endpoint: "http://127.0.0.1:9222", - allowed_origins: ["https://app.example.test"], - target_id: "page-1", - observation_ms: 120_000, - }); - expect(accepted.success).toBe(true); - expect( - observeWebSessionInputSchema.safeParse({ - cdp_endpoint: "http://127.0.0.1:9222", - allowed_origins: ["https://app.example.test"], - target_id: "page-1", - max_timeline_events: 1, - }).success, - ).toBe(false); - }); +import { trackBrowser } from "./cdpBrowserProvider.support.js"; +describe("CdpBrowserProvider navigation timeline", () => { it("returns every event beyond the former default collection cap", async () => { const additionalEvents = 2_001; const browser = await startFakeCdpBrowser({ diff --git a/tests/boundary/browser/cdpCaptureDocuments.test.ts b/tests/boundary/browser/cdpCaptureDocuments.test.ts index 92612d9be..9a11c4510 100644 --- a/tests/boundary/browser/cdpCaptureDocuments.test.ts +++ b/tests/boundary/browser/cdpCaptureDocuments.test.ts @@ -1,52 +1,61 @@ import { expect, it } from "vitest"; -import { CdpCaptureCompleteness } from "../../../src/browser/CdpCaptureCompleteness.js"; -import { - captureFrames, - captureResources, -} from "../../../src/browser/CdpCaptureDocuments.js"; +import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; +import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js"; +import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; +import { trackBrowser } from "./cdpBrowserProvider.support.js"; -it("reports malformed child frame entries instead of silently completing inventory", () => { - const completeness = new CdpCaptureCompleteness(); - const captured = captureFrames( - { - frameTree: { - frame: { - id: "main", - url: "https://example.test/", - }, - childFrames: [ - null, - { frame: { id: "child", url: "https://example.test/child" } }, - ], - }, - }, - new Set(["https://example.test"]), - undefined, - completeness, +it("reports malformed child frame entries through the CDP boundary", async () => { + const browser = await startFakeCdpBrowser({ + commandResult: (command, origin) => + command.method === "Page.getFrameTree" + ? { + frameTree: { + frame: { id: "main", url: `${origin}/` }, + childFrames: [ + null, + { frame: { id: "child", url: `${origin}/child` } }, + ], + }, + } + : undefined, + }); + trackBrowser(browser); + + const result = await new CdpBrowserProvider().inspectPage( + inspectWebPageInputSchema.parse({ + cdp_endpoint: browser.endpoint, + allowed_origins: [browser.allowedOrigin], + target_id: "allowed-page", + observation_ms: 0, + }), ); - expect(captured.items.map(({ frame_id }) => frame_id)).toEqual([ + if (!result.ok) throw result.error; + expect(result.value.frames.map(({ frame_id }) => frame_id)).toEqual([ "main", "child", ]); - expect(completeness.snapshot().unavailable_sections).toContain("frames"); + expect(result.value.completeness.unavailable_sections).toContain("frames"); }); -it("reports missing top-level frame trees for frame and resource inventories", () => { - const completeness = new CdpCaptureCompleteness(); - expect( - captureFrames( - {}, - new Set(["https://example.test"]), - undefined, - completeness, - ).items, - ).toEqual([]); - expect( - captureResources({}, new Set(["https://example.test"]), completeness).items, - ).toEqual([]); - expect(completeness.snapshot().unavailable_sections).toEqual( - expect.arrayContaining(["frames", "resources"]), +it("marks a missing resource frame tree incomplete through CDP", async () => { + const browser = await startFakeCdpBrowser({ + commandResult: (command) => + command.method === "Page.getResourceTree" ? {} : undefined, + }); + trackBrowser(browser); + + const result = await new CdpBrowserProvider().inspectPage( + inspectWebPageInputSchema.parse({ + cdp_endpoint: browser.endpoint, + allowed_origins: [browser.allowedOrigin], + target_id: "allowed-page", + observation_ms: 0, + }), ); + + if (!result.ok) throw result.error; + expect(result.value.resources).toEqual([]); + expect(result.value.completeness.unavailable_sections).toContain("resources"); }); diff --git a/tests/boundary/browser/cdpCaptureNavigation.test.ts b/tests/boundary/browser/cdpCaptureNavigation.test.ts index 070d6b112..471607640 100644 --- a/tests/boundary/browser/cdpCaptureNavigation.test.ts +++ b/tests/boundary/browser/cdpCaptureNavigation.test.ts @@ -1,65 +1,8 @@ import { expect, it, onTestFinished } from "vitest"; -import { CdpCaptureEvents } from "../../../src/browser/CdpCaptureEvents.js"; import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; -import type { CdpEvent } from "../../../src/browser/CdpConnection.js"; - -const events = (): CdpCaptureEvents => - new CdpCaptureEvents( - inspectWebPageInputSchema.parse({ - cdp_endpoint: "http://127.0.0.1:9222", - allowed_origins: ["https://example.test"], - target_id: "page-1", - }), - new Set(["https://example.test"]), - ); - -const frameNavigated = (url: string, loaderId: string): CdpEvent => ({ - method: "Page.frameNavigated", - params: { - frame: { id: "main", url, loaderId }, - }, -}); - -it("ignores a repeated frameNavigated for the document already committed", () => { - const state = events(); - state.beginAuthorizedFrame("main"); - state.ingest(frameNavigated("https://example.test/page", "loader-1")); - expect(state.navigationDuringCapture).toBe(true); - state.navigationDuringCapture = false; - - // A re-fire with the same URL and loader is not a navigation. - state.ingest(frameNavigated("https://example.test/page", "loader-1")); - expect(state.navigationDuringCapture).toBe(false); -}); - -it("still reports a genuine change of document", () => { - const state = events(); - state.beginAuthorizedFrame("main"); - state.ingest(frameNavigated("https://example.test/page", "loader-1")); - state.navigationDuringCapture = false; - state.ingest(frameNavigated("https://example.test/other", "loader-2")); - expect(state.navigationDuringCapture).toBe(true); -}); - -it("does not forget a navigation that happened during the observation window", () => { - const state = events(); - state.beginAuthorizedFrame("main"); - state.ingest(frameNavigated("https://example.test/page", "loader-1")); - // The final capture re-attaches to the resolved frame; clearing the flag here - // used to report a clean capture while carrying pre-navigation state. - state.beginFinalCapture("main"); - expect(state.navigationDuringCapture).toBe(true); -}); - -it("keeps a settled observation window clean", () => { - const state = events(); - state.beginAuthorizedFrame("main"); - state.beginFinalCapture("main"); - expect(state.navigationDuringCapture).toBe(false); -}); it("captures a page without aborting on a duplicate frame event", async () => { const browser = await startFakeCdpBrowser({ sessionTimeline: "same_origin" }); diff --git a/tests/boundary/browser/cdpElectronProvider.test.ts b/tests/boundary/browser/cdpElectronProvider.test.ts index b15ffaa43..3a8cdffdb 100644 --- a/tests/boundary/browser/cdpElectronProvider.test.ts +++ b/tests/boundary/browser/cdpElectronProvider.test.ts @@ -199,25 +199,6 @@ describe("CdpElectronProvider target selection", () => { }); }); -describe("Electron request schemas", () => { - it("rejects unknown request fields", () => { - expect( - listElectronTargetsInputSchema.safeParse({ - cdp_endpoint: "http://127.0.0.1:9223", - unknown_field: true, - }).success, - ).toBe(false); - expect( - inspectElectronPageInputSchema.safeParse({ - cdp_endpoint: "http://127.0.0.1:9223", - target_id: "page-1", - observation_ms: 0, - unknown_field: true, - }).success, - ).toBe(false); - }); -}); - describe("CdpElectronProvider capture", () => { it("captures Electron script source only after separate approval", async () => { const root = await electronFixture(); diff --git a/tests/boundary/browser/cdpWebRuntimeSourceIdentity.test.ts b/tests/boundary/browser/cdpWebRuntimeSourceIdentity.test.ts index 419b8fb31..e14b81d20 100644 --- a/tests/boundary/browser/cdpWebRuntimeSourceIdentity.test.ts +++ b/tests/boundary/browser/cdpWebRuntimeSourceIdentity.test.ts @@ -82,7 +82,12 @@ it.each( }); } else { const result = await provider.observeExecution( - observeWebExecutionInputSchema.parse({ ...scope, observation_ms: 5 }), + observeWebExecutionInputSchema.parse({ + ...scope, + // This fixture sends a real WebSocket event after the armed receipt. + // Leave room for transport delivery on a shared CI runner. + observation_ms: 100, + }), { progress: { report: async (update) => { @@ -124,8 +129,17 @@ it.each( ({ script_id }) => script_id === "script-a", ), ).toBe(false); - expect(result.value.requests[0]?.callsites[0]?.source_association).toBe( - "unknown", + expect(result.value.window.end_reason).toBe("window_elapsed"); + expect(result.value.requests).toContainEqual( + expect.objectContaining({ + request_id: "source-completion-request", + callsites: expect.arrayContaining([ + expect.objectContaining({ + script_id: "script-a", + source_association: "unknown", + }), + ]), + }), ); expect(result.value.script_inventory.main_document_scripts).toBe(1); expect( diff --git a/tests/boundary/browser/electronActiveHook.test.ts b/tests/boundary/browser/electronActiveHook.test.ts index 75ad44f83..2afaebc7a 100644 --- a/tests/boundary/browser/electronActiveHook.test.ts +++ b/tests/boundary/browser/electronActiveHook.test.ts @@ -122,8 +122,11 @@ it("retains active hook lifecycle and IPC evidence without a count ceiling", asy const script = join(root, "exercise.cjs"); await writeFile(script, activeCaptureSource); const hook = join(process.cwd(), "scripts/electron-active-hook.cjs"); - const output = await runNode(hook, script, root, root); - const result = JSON.parse(output.trim().split("\n").at(-1) ?? "null") as { + const execution = await runNode(hook, script, root, root); + expect(execution.code).toBe(0); + const result = JSON.parse( + execution.stdout.trim().split("\n").at(-1) ?? "null", + ) as { readonly default_popup_decision: { readonly action: string }; readonly popup_decision: { readonly action: string }; readonly snapshot: { @@ -181,7 +184,7 @@ it("preserves fatal process termination after recording uncaught exceptions", as const script = join(root, "fatal.cjs"); await writeFile(script, 'throw new Error("fatal-hook-secret");\n'); const hook = join(process.cwd(), "scripts/electron-active-hook.cjs"); - const result = await runNodeStatus(hook, script, root, root); + const result = await runNode(hook, script, root, root); expect(result.code).not.toBe(0); expect(result.stderr).toContain("fatal-hook-secret"); @@ -192,7 +195,11 @@ const runNode = ( script: string, moduleRoot: string, cwd: string, -): Promise => +): Promise<{ + readonly code: number | null; + readonly stdout: string; + readonly stderr: string; +}> => new Promise((resolve, reject) => { const child = spawn(process.execPath, ["-r", hook, script], { cwd, @@ -207,44 +214,11 @@ const runNode = ( child.stdout.on("data", (chunk: Buffer) => stdout.push(chunk)); child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk)); child.once("error", reject); - child.once("close", (code) => { - if (code === 0) { - const text = Buffer.concat(stdout).toString("utf8"); - if (text.length > 0) resolve(text); - else - reject( - new Error( - `hook fixture produced no output: ${Buffer.concat(stderr).toString("utf8")}`, - ), - ); - } else - reject( - new Error( - `hook fixture exited with ${String(code)}: ${Buffer.concat(stderr).toString("utf8")}`, - ), - ); - }); - }); - -const runNodeStatus = ( - hook: string, - script: string, - moduleRoot: string, - cwd: string, -): Promise<{ readonly code: number | null; readonly stderr: string }> => - new Promise((resolve, reject) => { - const child = spawn(process.execPath, ["-r", hook, script], { - cwd, - env: { - ...process.env, - NODE_PATH: moduleRoot, - }, - stdio: ["ignore", "pipe", "pipe"], - }); - const stderr: Buffer[] = []; - child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk)); - child.once("error", reject); child.once("close", (code) => - resolve({ code, stderr: Buffer.concat(stderr).toString("utf8") }), + resolve({ + code, + stdout: Buffer.concat(stdout).toString("utf8"), + stderr: Buffer.concat(stderr).toString("utf8"), + }), ); }); diff --git a/tests/boundary/browser/historicalCaptureDecoder.test.ts b/tests/boundary/browser/historicalCaptureDecoder.test.ts new file mode 100644 index 000000000..5187d1f6a --- /dev/null +++ b/tests/boundary/browser/historicalCaptureDecoder.test.ts @@ -0,0 +1,481 @@ +import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js"; +import { + access, + chmod, + mkdtemp, + rm, + truncate, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, it, onTestFinished } from "vitest"; +import { HistoricalCaptureDecoder } from "../../../src/browser/history/HistoricalCaptureDecoder.js"; +import { HAR_CAPTURE_PROVIDER_IDENTITY } from "../../../src/browser/history/CaptureRelease.js"; +import { + inspectWebNetworkCaptureInputSchema, + WEB_NETWORK_CAPTURE_LIMITS, +} from "../../../src/domain/webNetworkCapture.js"; +import { historicalHar } from "../../fixtures/historicalHar.js"; +import { decodeHarCapture } from "../../../src/browser/history/HarCapture.js"; +import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js"; + +it.each([true, false])( + "binds decoder replies to the complete selected provider identity (matching name: %s)", + async (matching) => { + const root = await mkdtemp(join(tmpdir(), "rea-capture-identity-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "capture.har"); + const text = JSON.stringify(historicalHar()); + await writeFile(path, text); + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async (_, runtimePath) => { + const value = decodeHarCapture(text, []); + if (!matching) value.decoder.name = "different adapter"; + await writeFile( + join(runtimePath, "reply.json"), + JSON.stringify({ ok: true, value }), + ); + return { command: process.execPath, arguments: ["-e", ""] }; + }, + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + }), + ); + expect(result.ok).toBe(matching); + if (!result.ok) expect(result.error._tag).toBe("AnalysisOutputError"); + else expect(result.value.decoder).toEqual(HAR_CAPTURE_PROVIDER_IDENTITY); + }, +); + +it("classifies an oversized selected capture before creating an owned decoder", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-limit-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "input.har"); + await writeFile(path, ""); + await truncate(path, WEB_NETWORK_CAPTURE_LIMITS.inputBytes + 1); + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async () => { + throw new Error("Oversized input must not launch decoding"); + }, + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + }), + ); + if (result.ok) throw new Error("Oversized input must fail"); + expect(result.error._tag).toBe("AnalysisInputError"); + expect(projectAnalysisError(result.error)).toMatchObject({ + category: "invalid_input", + details: { + issues: [ + { + path: ["capture_path"], + reason: "out_of_range", + expected: { + maximum_capture_bytes: WEB_NETWORK_CAPTURE_LIMITS.inputBytes, + }, + }, + ], + }, + }); +}); + +it("does not misclassify decoder filesystem failures as missing selected input", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-command-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "input.har"); + await writeFile(path, JSON.stringify(historicalHar())); + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async () => { + throw Object.assign( + new Error("Configured decoder command file missing"), + { code: "ENOENT" }, + ); + }, + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + }), + ); + if (result.ok) throw new Error("Failed decoder command must fail"); + expect(result.error._tag).toBe("ProviderAdapterError"); + expect(projectAnalysisError(result.error)).toMatchObject({ + details: { + diagnostics: { + phase: "decoder", + reason: "Configured decoder command file missing", + }, + }, + }); +}); + +it.each(["missing-capture", "missing-reply", "failed-command"])( + "redacts declared paths and diagnostics while preserving the %s failure", + async (mode) => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-sensitive-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "explicit-private-value.har"); + if (mode !== "missing-capture") + await writeFile(path, JSON.stringify(historicalHar())); + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async () => ({ + command: process.execPath, + arguments: [ + "-e", + mode === "failed-command" + ? 'process.stderr.write("explicit-private-value ordinary diagnostic"); process.exit(2)' + : "process.exit(0)", + ], + }), + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + sensitive_values: ["explicit-private-value", "REDACTED"], + }), + ); + if (result.ok) throw new Error("Failure required"); + const projected = projectAnalysisError(result.error); + const serialized = JSON.stringify(projected); + expect(serialized).not.toContain("explicit-private-value"); + expect(serialized).not.toContain("REDACTED"); + expect(result.error._tag).toBe( + mode === "missing-capture" + ? "AnalysisInputError" + : mode === "missing-reply" + ? "AnalysisOutputError" + : "ProviderAdapterError", + ); + if (mode === "missing-capture") expect(serialized).toContain("ENOENT"); + if (mode === "failed-command") + expect(serialized).toContain('"exit_code":2'); + }, +); + +it("reports an absent owned reply as output failure and removes its private snapshot root", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-decoder-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "input.har"); + await writeFile(path, JSON.stringify(historicalHar())); + let privatePath: string | undefined; + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async (_, runtimePath) => { + privatePath = runtimePath; + return { + command: process.execPath, + arguments: ["-e", "process.exit(0)"], + }; + }, + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + }), + ); + if (result.ok) throw new Error("Owned reply is required"); + expect(result.error._tag).toBe("AnalysisOutputError"); + expect(result.error.message).toContain("ENOENT"); + if (privatePath === undefined) + throw new Error("Private root was not acquired"); + await expect(access(privatePath)).rejects.toMatchObject({ code: "ENOENT" }); +}); + +it("cancels after snapshot acquisition before launch and removes private input declarations", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-cancel-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "input.har"); + await writeFile(path, JSON.stringify(historicalHar())); + let privatePath: string | undefined; + const controller = new AbortController(); + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async (_, runtimePath) => { + privatePath = runtimePath; + controller.abort(); + return { + command: process.execPath, + arguments: ["-e", "process.exit(3)"], + }; + }, + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + sensitive_values: ["explicit-private-declaration"], + }), + { signal: controller.signal }, + ); + if (result.ok) throw new Error("Cancelled acquisition must fail"); + expect(result.error._tag).toBe("AnalysisCancelledError"); + if (privatePath === undefined) + throw new Error("Private root was not acquired"); + await expect(access(privatePath)).rejects.toMatchObject({ code: "ENOENT" }); +}); + +it.each([ + { reason: "input-limit", tag: "AnalysisInputError" }, + { reason: "resource-limit", tag: "ProviderAdapterError" }, + { reason: "decoder", tag: "ProviderAdapterError" }, + { reason: "limit", tag: "AnalysisOutputError" }, +])( + "preserves $reason decoder failures through the public error boundary", + async ({ reason, tag }) => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-reply-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "input.har"); + await writeFile(path, JSON.stringify(historicalHar())); + let privatePath: string | undefined; + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async (_, runtimePath) => { + privatePath = runtimePath; + await writeFile( + join(runtimePath, "reply.json"), + JSON.stringify({ + ok: false, + reason, + message: "Specific decoder constraint", + pointer: "/extension/child", + }), + ); + return { + command: process.execPath, + arguments: ["-e", "process.exit(0)"], + }; + }, + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + }), + ); + if (result.ok) throw new Error("Failure required"); + expect(result.error._tag).toBe(tag); + const projected = projectAnalysisError(result.error); + if (reason === "input-limit") { + expect(projected).toMatchObject({ + category: "invalid_input", + details: { + issues: [ + { + path: ["capture_path", "/extension/child"], + reason: "out_of_range", + expected: { + maximum_capture_nesting: WEB_NETWORK_CAPTURE_LIMITS.depth, + }, + }, + ], + }, + }); + } else if (tag === "ProviderAdapterError") { + expect(projected).toMatchObject({ + details: { + diagnostics: { + phase: "decoder", + reason: "Specific decoder constraint", + failure_kind: reason, + pointer: "/extension/child", + }, + }, + }); + } + if (privatePath === undefined) + throw new Error("Private root was not acquired"); + await expect(access(privatePath)).rejects.toMatchObject({ code: "ENOENT" }); + }, +); + +it.skipIf(process.platform === "win32" || process.getuid?.() === 0)( + "reports a real filesystem permission denial separately from missing capture input", + async () => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-denied-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "unreadable.har"); + await writeFile(path, JSON.stringify(historicalHar()), { mode: 0o000 }); + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async () => { + throw new Error("Permission-denied input must not launch decoding"); + }, + }, + ], + process.env, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + }), + ); + await chmod(path, 0o600); + if (result.ok) throw new Error("Permission denial required"); + expect(projectAnalysisError(result.error)).toMatchObject({ + code: "access_denied", + category: "unavailable", + retryable: false, + details: { + operation: "inspect_web_network_capture", + path, + system_code: "EACCES", + }, + }); + expect(projectAnalysisError(result.error).remediation.action).toContain( + "read access", + ); + }, +); + +it.each(["format", "process"])( + "preserves the structured %s failure when runtime cleanup fails", + async (kind) => { + const root = await mkdtemp(join(tmpdir(), "rea-historical-cleanup-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "capture-private.har"); + await writeFile(path, JSON.stringify(historicalHar())); + const decoder = new HistoricalCaptureDecoder( + [ + { + format: "har", + identity: HAR_CAPTURE_PROVIDER_IDENTITY, + command: async (_, runtimePath) => { + if (kind === "format") + await writeFile( + join(runtimePath, "reply.json"), + JSON.stringify({ + ok: false, + reason: "format", + message: "Malformed retained record", + pointer: "/metadata/capture-private", + }), + ); + return { + command: process.execPath, + arguments: [ + "-e", + kind === "format" + ? "process.exit(0)" + : 'process.stderr.write("Specific provider fault"); process.exit(3)', + ], + }; + }, + }, + ], + process.env, + async () => { + const runtime = await PrivateRuntimeRoot.create({ + prefix: "rea-cleanup-proof-", + }); + onTestFinished(() => runtime.close()); + return { + path: runtime.path, + close: async () => { + throw new Error("Deliberate cleanup denial"); + }, + }; + }, + ); + const result = await decoder.inspect( + inspectWebNetworkCaptureInputSchema.parse({ + capture_path: path, + format: "har", + sensitive_values: ["capture-private"], + }), + ); + if (result.ok) throw new Error("Cleanup failure required"); + const projected = projectAnalysisError(result.error); + expect(projected).toMatchObject({ + code: "cleanup_incomplete", + details: { + diagnostics: { + previous_error: + kind === "format" + ? { + category: "invalid_input", + details: { + issues: [ + { + path: ["capture_path", "/metadata"], + reason: "invalid_format", + message: "Malformed retained record", + }, + ], + }, + } + : { + code: "execution_failure", + details: { + diagnostics: { + phase: "decoder", + exit_code: 3, + stderr: "Specific provider fault", + }, + }, + }, + }, + }, + }); + expect(JSON.stringify(projected)).not.toContain("capture-private"); + }, +); diff --git a/tests/boundary/browser/historicalWebCapture.test.ts b/tests/boundary/browser/historicalWebCapture.test.ts new file mode 100644 index 000000000..a6905bdb3 --- /dev/null +++ b/tests/boundary/browser/historicalWebCapture.test.ts @@ -0,0 +1,109 @@ +import { createHash } from "node:crypto"; +import { mkdtemp, writeFile, rm } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { expect, it, onTestFinished } from "vitest"; +import { execFile } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { parseEvidence } from "../../../src/domain/evidence.js"; +const entrypoint = fileURLToPath( + new URL("../../../scripts/rea.mjs", import.meta.url), +); +const cli = async ( + path: string, + format: string, + extra: readonly string[] = [], +) => { + try { + const result = await promisify(execFile)( + process.execPath, + [ + entrypoint, + "inspect-web-network-capture", + path, + format, + "--json", + ...extra, + ], + { env: { ...process.env, REA_MITMDUMP_COMMAND: "" }, timeout: 40_000 }, + ); + return { ok: true, stdout: result.stdout }; + } catch (cause: unknown) { + if ( + cause instanceof Error && + "stdout" in cause && + typeof cause.stdout === "string" + ) + return { ok: false, stdout: cause.stdout }; + throw cause; + } +}; +import { historicalHar } from "../../fixtures/historicalHar.js"; + +it("reads actual upstream HAR packages in an owned process and retains selected producer order", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-history-har-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const fixture = historicalHar(); + const first = fixture.log.entries[0]; + if (first === undefined) throw new Error("fixture missing"); + fixture.log.entries.push(structuredClone(first)); + const text = JSON.stringify(fixture); + const path = join(root, "capture.har"); + await writeFile(path, text); + const called = await cli(path, "har", ["--record", "1", "--record", "0"]); + expect(called.ok, called.stdout).toBe(true); + const result = { value: parseEvidence(JSON.parse(called.stdout)) }; + expect(result.value.subject?.digest).toMatchObject({ + sha256: createHash("sha256").update(text).digest("hex"), + }); + expect(result.value.normalized_result).toMatchObject({ + total_records: 2, + records: [{ ordinal: 1 }, { ordinal: 0 }], + runtime_attribution: "unknown", + }); +}); + +it("keeps malformed JSON separate from absent external mitmproxy capability", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-history-errors-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + const path = join(root, "capture.har"); + await writeFile(path, "{malformed-private-json"); + const malformed = await cli(path, "har"); + expect(malformed.ok).toBe(false); + expect(JSON.parse(malformed.stdout)).toMatchObject({ + category: "invalid_input", + }); + expect(malformed.stdout).not.toContain("malformed-private-json"); + const missing = await cli(path, "mitmproxy"); + expect(missing.ok).toBe(false); + expect(JSON.parse(missing.stdout)).toMatchObject({ + category: "unsupported_provider", + }); +}); + +it("reports the actual HAR decoder nesting constraint as a typed selected-input issue", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-history-depth-")); + onTestFinished(() => rm(root, { recursive: true, force: true })); + let extension: unknown = "leaf"; + for (let depth = 0; depth < 66; depth++) extension = { child: extension }; + const path = join(root, "deep.har"); + await writeFile( + path, + JSON.stringify({ ...historicalHar(), _extension: extension }), + ); + const result = await cli(path, "har"); + expect(result.ok).toBe(false); + expect(JSON.parse(result.stdout)).toMatchObject({ + category: "invalid_input", + details: { + issues: [ + { + path: ["capture_path", expect.stringMatching(/^\/_extension\/child/)], + reason: "out_of_range", + expected: { maximum_capture_nesting: 64 }, + }, + ], + }, + }); +}); diff --git a/tests/boundary/browser/webCaptureDiff.test.ts b/tests/boundary/browser/webCaptureDiff.test.ts index 813b4d680..935bc7ae5 100644 --- a/tests/boundary/browser/webCaptureDiff.test.ts +++ b/tests/boundary/browser/webCaptureDiff.test.ts @@ -22,16 +22,6 @@ afterEach(async () => { }); describe("web capture diff", () => { - it("rejects caller limits that would omit difference records", () => { - expect( - compareWebCapturesInputSchema.safeParse({ - before: {}, - after: {}, - max_changes: 1, - }).success, - ).toBe(false); - }); - it("reports stable observed changes while preserving unknown dimensions", async () => { const browser = await startFakeCdpBrowser(); browsers.push(browser); @@ -48,7 +38,22 @@ describe("web capture diff", () => { const after = structuredClone(captured.value); after.scripts.items = []; const request = after.network.requests[0]; - if (request !== undefined) request.status = 204; + if (request !== undefined) { + request.status = 204; + request.redirects = [ + { + url: `${browser.allowedOrigin}/prior`, + response_url: `${browser.allowedOrigin}/prior`, + method: "GET", + resource_type: "Fetch", + status: 302, + mime_type: "text/plain", + encoded_data_length: 12, + request_timestamp: 1, + redirect_event_timestamp: 2, + }, + ]; + } const result = compareWebCaptures( compareWebCapturesInputSchema.parse({ @@ -163,6 +168,68 @@ describe("web capture diff", () => { }); }); +describe("web capture redirect comparison", () => { + it("ignores redirect event times but compares redirect semantics", async () => { + const browser = await startFakeCdpBrowser(); + browsers.push(browser); + const captured = await new CdpBrowserProvider().inspectPage( + inspectWebPageInputSchema.parse({ + cdp_endpoint: browser.endpoint, + allowed_origins: [browser.allowedOrigin], + target_id: "allowed-page", + observation_ms: 0, + }), + ); + if (!captured.ok) throw captured.error; + const before = structuredClone(captured.value); + const beforeRequest = before.network.requests[0]; + if (beforeRequest === undefined) throw new Error("Missing network request"); + beforeRequest.redirects = [ + { + url: `${browser.allowedOrigin}/prior`, + response_url: `${browser.allowedOrigin}/prior`, + method: "GET", + resource_type: "Fetch", + status: 302, + mime_type: "text/plain", + encoded_data_length: 12, + request_timestamp: 1, + redirect_event_timestamp: 2, + }, + ]; + markSectionsComplete(before, ["network_requests"]); + + const after = structuredClone(before); + const afterRequest = after.network.requests[0]; + if (afterRequest === undefined) throw new Error("Missing network request"); + const hop = afterRequest.redirects?.[0]; + if (hop === undefined) throw new Error("Missing redirect hop"); + hop.request_timestamp = 10; + hop.redirect_event_timestamp = 20; + + const compare = (inspection: typeof before) => + compareWebCaptures( + compareWebCapturesInputSchema.parse({ + before: { inspection: before }, + after: { inspection }, + }), + ).dimensions.network; + expect(compare(after)).toMatchObject({ + status: "unchanged", + total_changes: 0, + }); + + const changed = structuredClone(after); + const changedHop = changed.network.requests[0]?.redirects?.[0]; + if (changedHop === undefined) throw new Error("Missing redirect hop"); + changedHop.status = 307; + expect(compare(changed)).toMatchObject({ + status: "changed", + changes: [expect.objectContaining({ change: "modified" })], + }); + }); +}); + describe("web capture diff incomplete inventories", () => { it.each(["truncated_sections", "unavailable_sections"] as const)( "does not infer script additions or removals from %s", diff --git a/tests/boundary/cli/browserCli.test.ts b/tests/boundary/cli/browserCli.test.ts index 6aab7fe3e..9ddd0ac9c 100644 --- a/tests/boundary/cli/browserCli.test.ts +++ b/tests/boundary/cli/browserCli.test.ts @@ -108,6 +108,57 @@ describe("browser CLI parity", () => { alignment: { status: "aligned", aligned_steps: 2 }, }, }); + const intentionalPolicy = await runCli( + [ + "compare-web-captures", + JSON.stringify(scenarioCapture), + JSON.stringify(scenarioCapture), + "--normalization-json", + JSON.stringify({ + rules: [ + { + rule_id: "domain", + artifacts: ["url"], + match: "app.example.test", + replacement: "normalized.test", + }, + ], + }), + "--json", + ], + process.env, + ); + expect(intentionalPolicy).toMatchObject({ + normalized_result: { + normalization: { rules: [{ rule_id: "domain" }] }, + }, + }); + + const malformedPolicy = await runCli( + [ + "compare-web-captures", + JSON.stringify(scenarioCapture), + JSON.stringify(scenarioCapture), + "--normalization-json", + "{invalid-policy", + "--json", + ], + process.env, + ); + expect(malformedPolicy).toMatchObject({ + error: "Browser observation failed", + code: "invalid_request", + details: { + operation: "compare_web_captures", + issues: [ + { + path: ["normalization"], + reason: "invalid_format", + expected: "JSON", + }, + ], + }, + }); }, INTEGRATION_TEST_TIMEOUT_MS, ); diff --git a/tests/boundary/cli/browserRedirectShapeCompleteness.test.ts b/tests/boundary/cli/browserRedirectShapeCompleteness.test.ts new file mode 100644 index 000000000..6608ce08d --- /dev/null +++ b/tests/boundary/cli/browserRedirectShapeCompleteness.test.ts @@ -0,0 +1,113 @@ +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; + +import { afterEach, expect, it } from "vitest"; + +import { + startFakeCdpBrowser, + type FakeCdpBrowser, +} from "../../fixtures/fakeCdpBrowser.js"; + +const execute = promisify(execFile); +const browsers: FakeCdpBrowser[] = []; + +afterEach(async () => { + await Promise.all(browsers.splice(0).map(async (browser) => browser.close())); +}); + +it("reports missing JSON shapes only when body shapes are selected", async () => { + const browser = await startFakeCdpBrowser({ + malformedRedirectResponse: true, + redirectResponseEnvelope: null, + }); + browsers.push(browser); + const commonArgs = [ + "inspect-web-page", + browser.endpoint, + "allowed-page", + "--allowed-origins", + browser.allowedOrigin, + "--observation-ms", + "0", + ]; + const selected = normalizedResult( + await runCli([...commonArgs, "--include-json-body-shapes", "--json"]), + ); + expect(selected).toMatchObject({ + network: { + requests: [ + { + body_shapes: { + status: "partial", + request: expect.any(Object), + response: null, + }, + }, + ], + }, + completeness: { + unavailable_sections: expect.arrayContaining([ + "network_requests", + "json_body_shapes", + ]), + excluded: expect.arrayContaining([ + { + section: "json_body_shapes", + reason: "invalid_protocol_value", + count: expect.any(Number), + }, + ]), + }, + }); + + const defaulted = normalizedResult(await runCli([...commonArgs, "--json"])); + expect(defaulted).toMatchObject({ + network: { + requests: [ + { + body_shapes: { + status: "not_approved", + request: null, + response: null, + }, + }, + ], + }, + }); + expect(defaulted.completeness).toMatchObject({ + unavailable_sections: expect.not.arrayContaining(["json_body_shapes"]), + }); +}); + +const runCli = async (arguments_: readonly string[]): Promise => { + try { + const { stdout } = await execute( + process.execPath, + ["scripts/rea.mjs", ...arguments_], + { + cwd: process.cwd(), + env: process.env, + maxBuffer: 16 * 1_024 * 1_024, + }, + ); + return JSON.parse(stdout); + } catch (cause: unknown) { + if ( + typeof cause === "object" && + cause !== null && + "stdout" in cause && + typeof cause.stdout === "string" + ) + return JSON.parse(cause.stdout); + throw cause; + } +}; + +const normalizedResult = (value: unknown): Record => { + if (!isRecord(value) || !isRecord(value.normalized_result)) + throw new TypeError("Missing CLI normalized result"); + return value.normalized_result; +}; + +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null && !Array.isArray(value); diff --git a/tests/boundary/cli/cliOutput.test.ts b/tests/boundary/cli/cliOutput.test.ts index 22fa1b0a6..783097b02 100644 --- a/tests/boundary/cli/cliOutput.test.ts +++ b/tests/boundary/cli/cliOutput.test.ts @@ -57,7 +57,9 @@ describe("CLI output argument and sanitization boundary", () => { validateCliOutputArguments(["providers", "--token-count", "--json"]), ).toEqual({ ok: true }); }); +}); +describe("CLI output valued-flag parsing", () => { workspaceCliTest( "preserves complete JSON errors when native builtin flags follow unusual arguments", async ({ cli }) => { @@ -92,6 +94,70 @@ describe("CLI output argument and sanitization boundary", () => { CLI_INTEGRATION_TIMEOUT_MS, ); + workspaceCliTest( + "does not treat empty or equals-style output values as active builtins", + async ({ cli }) => { + for (const arguments_ of [ + ["capabilities", "--json", "--format", ""], + ["capabilities", "--json", "--filter-output", ""], + ["capabilities", "--json", "--token-limit", ""], + ["capabilities", "--json", "--token-limit=5"], + ]) { + const result = await cli.run({ arguments: arguments_ }); + expect(result.exitCode).toBe(1); + expect(result.json).toBeDefined(); + expect(result.stdout).not.toContain("[truncated:"); + expect(JSON.stringify(result.json)).not.toContain( + "UNSUPPORTED_OUTPUT_COMBINATION", + ); + } + const validTokenWindow = await cli.run({ + arguments: ["capabilities", "--json", "--token-limit", "5"], + }); + expect(validTokenWindow.exitCode).toBe(1); + expect(validTokenWindow.json).toMatchObject({ + ok: false, + error: { code: "UNSUPPORTED_OUTPUT_COMBINATION" }, + }); + const emptyFormatWithTokenWindow = await cli.run({ + arguments: [ + "capabilities", + "--json", + "--format", + "", + "--token-limit", + "5", + ], + }); + expect(emptyFormatWithTokenWindow.exitCode).toBe(1); + expect(emptyFormatWithTokenWindow.stdout).not.toContain("[truncated:"); + expect(emptyFormatWithTokenWindow.json).toMatchObject({ + ok: false, + error: { code: "UNSUPPORTED_OUTPUT_COMBINATION" }, + }); + + for (const arguments_ of [ + ["capabilities", "--json", "--token-limit", "not-a-number"], + [ + "capabilities", + "--format", + "unsupported-format", + "--json", + "--token-limit", + "5", + ], + ]) { + const result = await cli.run({ arguments: arguments_ }); + expect(result.exitCode).toBe(1); + expect(result.stdout).not.toContain("UNSUPPORTED_OUTPUT_COMBINATION"); + expect(result.stdout).not.toContain("[truncated:"); + } + }, + CLI_INTEGRATION_TIMEOUT_MS, + ); +}); + +describe("CLI output argument and sanitization boundary", () => { it("preserves normal output and sanitizes text and JSON validation errors", () => { expect(sanitizeCliOutput("result: ok\n")).toBe("result: ok\n"); expect(sanitizeCliOutput("result: VALIDATION_ERROR\n")).toBe( @@ -179,7 +245,7 @@ describe("compiled CLI output boundary", () => { expect(result).toMatchObject({ exitCode: 0, stdout: "{}\n", - stderr: "", + stderr: expect.stringContaining('"rea_progress":'), json: {}, }); const nested = await cli.run({ @@ -194,7 +260,7 @@ describe("compiled CLI output boundary", () => { }); expect(nested).toMatchObject({ exitCode: 0, - stderr: "", + stderr: expect.stringContaining('"rea_progress":'), json: { normalized_result: {} }, }); const selected = await cli.run({ @@ -209,7 +275,7 @@ describe("compiled CLI output boundary", () => { }); expect(selected).toMatchObject({ exitCode: 0, - stderr: "", + stderr: expect.stringContaining('"rea_progress":'), json: { normalized_result: { summary: expect.objectContaining({ browser_windows: 0 }), diff --git a/tests/boundary/cli/dispatcher.test.ts b/tests/boundary/cli/dispatcher.test.ts index d94cb7ec7..79d2913b9 100644 --- a/tests/boundary/cli/dispatcher.test.ts +++ b/tests/boundary/cli/dispatcher.test.ts @@ -11,6 +11,7 @@ describe("executable dispatcher", () => { async ({ processes, workspace }) => { const scripts = await workspace.mkdir("scripts"); await workspace.mkdir("dist"); + await workspace.mkdir("dist/cli"); await Promise.all([ copyFile("scripts/rea.mjs", join(scripts, "rea.mjs")), workspace.write( @@ -29,6 +30,10 @@ describe("executable dispatcher", () => { "dist/cliOutput.js", "export const sanitizeCliOutput = (output) => output; export const validateCliOutputArguments = () => ({ ok: true }); export const renderCliOutputArgumentError = () => ''; export const renderEmptyFilteredCliOutput = () => undefined;\n", ), + workspace.write( + "dist/cli/streamedJsonOutput.js", + "export const createStreamedCliJsonOutput = () => undefined;\n", + ), workspace.write( "dist/mcpDoctor.js", 'export const runProductionMcpDoctorCli = (args) => Promise.resolve({ output: JSON.stringify({ adapter: "mcp-doctor", args }), exitCode: 0 });\n', diff --git a/tests/boundary/cli/javascriptApplicationJsonOutput.test.ts b/tests/boundary/cli/javascriptApplicationJsonOutput.test.ts new file mode 100644 index 000000000..05dc4fc06 --- /dev/null +++ b/tests/boundary/cli/javascriptApplicationJsonOutput.test.ts @@ -0,0 +1,118 @@ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { Filter } from "incur"; +import { expect } from "vitest"; +import { z } from "zod"; + +import { analyzeJavaScriptApplication } from "../../../src/application/javascript/JavaScriptApplicationService.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { cliTest } from "../../support/cli/cliFixture.js"; + +const filter = "evidence_id,normalized_result.graph.nodes[0:1].node_id"; + +for (const command of ["analyze-javascript-application", "analyze"]) { + for (const format of ["json", "jsonl"]) { + for (const fullOutput of [false, true]) { + for (const filtered of [false, true]) { + cliTest( + `${command} preserves ${format}, full output ${String(fullOutput)}, filtering ${String(filtered)}`, + async ({ cli }) => { + const root = await createTestTempDirectory("rea-js-json-output-"); + await writeFile(join(root, "main.js"), "fetch('');\n"); + const direct = await analyzeJavaScriptApplication({ + input_path: root, + }); + if (!direct.ok) throw direct.error; + const output = await cli.run({ + arguments: [ + command, + root, + "--format", + format, + ...(fullOutput ? ["--full-output"] : []), + ...(filtered ? ["--filter-output", filter] : []), + ], + }); + expect(output.exitCode).toBe(0); + let data: unknown = output.json; + if (fullOutput) { + const envelope = z + .object({ + ok: z.literal(true), + data: z.unknown(), + meta: z.object({ + command: z.literal(command), + duration: z.string().regex(/^\d+ms$/u), + }), + }) + .parse(output.json); + data = envelope.data; + } + expect(data).toEqual( + filtered + ? Filter.apply(direct.value, Filter.parse(filter)) + : direct.value, + ); + }, + ); + } + } + } +} + +cliTest( + "preserves a complete empty filtered JSON document", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-js-json-empty-filter-"); + await writeFile(join(root, "main.js"), "export const value = 1;\n"); + const output = await cli.run({ + arguments: [ + "analyze-javascript-application", + root, + "--json", + "--filter-output", + "absent.field", + ], + }); + expect(output.exitCode).toBe(0); + expect(output.json).toEqual({}); + }, +); + +cliTest( + "preserves a typed failed analysis in the JSON result", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-js-json-failure-"); + const output = await cli.run({ + arguments: [ + "analyze-javascript-application", + join(root, "absent"), + "--json", + ], + }); + expect(output.json).toMatchObject({ + code: "artifact_operation_failed", + details: { reason: "io" }, + }); + }, +); + +cliTest( + "preserves JSON omission for inherited filter methods", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-js-json-inherited-filter-"); + await writeFile(join(root, "main.js"), "export const value = 1;\n"); + const output = await cli.run({ + arguments: [ + "analyze-javascript-application", + root, + "--json", + "--filter-output", + "toString", + ], + }); + expect(output.exitCode).toBe(0); + expect(output.json).toEqual({}); + }, +); diff --git a/tests/boundary/cli/javascriptApplicationProgress.test.ts b/tests/boundary/cli/javascriptApplicationProgress.test.ts new file mode 100644 index 000000000..a4a0863e4 --- /dev/null +++ b/tests/boundary/cli/javascriptApplicationProgress.test.ts @@ -0,0 +1,72 @@ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { expect } from "vitest"; +import { z } from "zod"; + +import { parseEvidence } from "../../../src/domain/evidence.js"; +import { analyzeJavaScriptApplication } from "../../../src/application/javascript/JavaScriptApplicationService.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { cliTest } from "../../support/cli/cliFixture.js"; + +const progressLine = z.object({ + rea_progress: z.object({ + phase: z.string(), + completed: z.number(), + total: z.number().nullable(), + message: z.string(), + sequence: z.number(), + terminal: z.boolean().optional(), + }), +}); + +for (const command of ["analyze-javascript-application", "analyze"]) { + cliTest( + `${command} reports real analysis phases on stderr and preserves stdout Evidence`, + async ({ cli }) => { + const root = await createTestTempDirectory("rea-js-cli-progress-"); + await writeFile(join(root, "main.js"), "fetch('');\n"); + const direct = await analyzeJavaScriptApplication({ input_path: root }); + if (!direct.ok) throw direct.error; + + const output = await cli.run({ arguments: [command, root, "--json"] }); + expect(output.exitCode).toBe(0); + expect(parseEvidence(output.json).evidence_id).toBe( + direct.value.evidence_id, + ); + const updates = output.stderr + .trim() + .split("\n") + .map((line) => progressLine.parse(JSON.parse(line)).rea_progress); + expect(updates[0]?.completed).toBe(0); + expect( + updates.some(({ phase }) => phase === "parse_javascript_sources"), + ).toBe(true); + expect(updates.at(-1)).toMatchObject({ completed: 1, terminal: true }); + expect( + updates.slice(0, -1).every(({ terminal }) => terminal !== true), + ).toBe(true); + }, + ); +} + +cliTest( + "keeps a failed analysis distinct from a completed result", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-js-cli-progress-failure-"); + const output = await cli.run({ + arguments: [ + "analyze-javascript-application", + join(root, "absent"), + "--json", + ], + }); + expect(output.json).toMatchObject({ code: "artifact_operation_failed" }); + const updates = output.stderr + .trim() + .split("\n") + .map((line) => progressLine.parse(JSON.parse(line)).rea_progress); + expect(updates[0]?.completed).toBe(0); + expect(updates.every(({ terminal }) => terminal !== true)).toBe(true); + }, +); diff --git a/tests/boundary/cli/jsonInputStatus.test.ts b/tests/boundary/cli/jsonInputStatus.test.ts index f95c9d54b..1acd8c61b 100644 --- a/tests/boundary/cli/jsonInputStatus.test.ts +++ b/tests/boundary/cli/jsonInputStatus.test.ts @@ -1,9 +1,8 @@ -import { writeFile } from "node:fs/promises"; +import { chmod, mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { describe, expect } from "vitest"; -import { parseCliJsonInput } from "../../../src/cliJsonInput.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest } from "../../support/cli/cliFixture.js"; @@ -30,46 +29,63 @@ const JSON_COMMANDS = [ describe("compiled CLI JSON input failure status", () => { for (const [command, operation] of JSON_COMMANDS) - for (const kind of ["inline", "file", "missing"] as const) - cliTest( - `${command} rejects malformed or unreadable ${kind} input before dispatch`, - async ({ cli }) => { - const root = await createTestTempDirectory("rea-cli-json-status-"); - const input = kind === "inline" ? "{" : join(root, "input.json"); - if (kind === "file") await writeFile(input, "{"); - const parsed = await parseCliJsonInput(input, operation); - if (parsed.ok) - throw new Error("Expected JSON input to fail before dispatch"); + cliTest( + `${command} rejects malformed inline input before dispatch`, + async ({ cli }) => { + const root = await createTestTempDirectory("rea-cli-json-status-"); + const input = "{"; + const result = await cli.run({ + arguments: [command, input, "--json"], + environment: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }); - const result = await cli.run({ - arguments: [command, input, "--json"], - environment: { - HOME: root, - XDG_CONFIG_HOME: root, - XDG_CACHE_HOME: root, - }, - }); + expect(result.json).toMatchObject({ + code: "invalid_request", + category: "invalid_input", + details: { operation }, + }); + expect(result.stderr).toBe(""); + expect(result.exitCode).toBe(1); + }, + ); - expect(result.stdout).toBe( - `${JSON.stringify(parsed.error, null, 2)}\n`, - ); - expect(result.json).toMatchObject({ - code: "invalid_request", - category: "invalid_input", - ...(kind === "missing" ? {} : { details: { operation } }), - ...(kind === "inline" - ? {} - : { - input_path: input, - input_reason: - kind === "file" ? "invalid-json" : "read-failed", - }), - }); - expect(result.stderr).toBe(""); - expect(result.exitCode).toBe(1); - }, - ); + for (const kind of ["file", "missing"] as const) + cliTest( + `preserves ${kind} JSON input diagnostics through a real command`, + async ({ cli }) => { + const root = await createTestTempDirectory("rea-cli-json-status-"); + const input = join(root, "input.json"); + if (kind === "file") await writeFile(input, "{"); + const result = await cli.run({ + arguments: ["compare-javascript-export-shapes", input, "--json"], + environment: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }); + + expect(result.json).toMatchObject({ + code: "invalid_request", + category: "invalid_input", + input_path: input, + input_reason: kind === "file" ? "invalid-json" : "read-failed", + ...(kind === "file" + ? { details: { operation: "compare-javascript-export-shapes" } } + : {}), + }); + expect(result.stderr).toBe(""); + expect(result.exitCode).toBe(1); + }, + ); +}); + +describe("compiled CLI JSON input diagnostic preservation", () => { for (const [format, flags] of [ ["JSON", ["--json"]], ["JSONL", ["--format", "jsonl"]], @@ -113,7 +129,7 @@ describe("compiled CLI JSON input failure status", () => { XDG_CACHE_HOME: root, }, }); - expect(result.json).toMatchObject({ + expect(JSON.parse(result.stdout.trim())).toMatchObject({ code: "invalid_request", input_path: "", input_reason: "read-failed", @@ -121,6 +137,125 @@ describe("compiled CLI JSON input failure status", () => { expect(result.exitCode).toBe(1); }, ); + + cliTest( + "classifies an overlong malformed inline value before dispatch", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-cli-json-overlong-"); + const input = `[${"x".repeat(3_000)}`; + const result = await cli.run({ + arguments: ["compare-javascript-export-shapes", input, "--json"], + environment: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }); + + expect(result.json).toMatchObject({ + code: "invalid_request", + category: "invalid_input", + details: { + operation: "compare-javascript-export-shapes", + issues: [{ path: [], reason: "invalid_format", expected: "JSON" }], + }, + }); + expect(result.json).not.toHaveProperty("input_path"); + expect(result.stdout).not.toContain("read-failed"); + expect(result.stderr).toBe(""); + expect(result.exitCode).toBe(1); + }, + ); + + cliTest( + "preserves explicit overlong JSON path diagnostics", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-cli-json-overlong-path-"); + const input = `[${"x".repeat(3_000)}.json`; + const result = await cli.run({ + arguments: ["compare-javascript-export-shapes", input, "--json"], + environment: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }); + + expect(result.json).toMatchObject({ + code: "invalid_request", + input_path: input, + input_reason: "read-failed", + }); + expect(result.exitCode).toBe(1); + }, + ); +}); + +describe("compiled CLI JSON path ambiguity", () => { + cliTest( + "classifies malformed inline JSON when a path prefix is a regular file", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-cli-json-not-dir-"); + await writeFile(join(root, "[prefix"), "regular file"); + const input = "[prefix/rest"; + const result = await cli.run({ + arguments: ["capture-browser-scenario", input, "--json"], + cwd: root, + environment: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }); + + expect(result.json).toMatchObject({ + code: "invalid_request", + category: "invalid_input", + details: { + operation: "capture_browser_scenario", + issues: [{ path: [], reason: "invalid_format", expected: "JSON" }], + }, + }); + expect(result.json).not.toHaveProperty("input_path"); + expect(result.stdout).not.toContain("read-failed"); + expect(result.stderr).toBe(""); + expect(result.exitCode).toBe(1); + }, + ); + + cliTest.runIf(process.platform !== "win32" && process.getuid?.() !== 0)( + "preserves permission errors for malformed path-like JSON input", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-cli-json-denied-"); + const denied = join(root, "[locked"); + const input = "[locked/rest"; + await mkdir(denied); + await chmod(denied, 0); + try { + await expect(readFile(join(root, input))).rejects.toMatchObject({ + code: "EACCES", + }); + const result = await cli.run({ + arguments: ["capture-browser-scenario", input, "--json"], + cwd: root, + environment: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }); + + expect(result.json).toMatchObject({ + code: "invalid_request", + input_path: input, + input_reason: "read-failed", + }); + expect(result.exitCode).toBe(1); + } finally { + await chmod(denied, 0o700); + } + }, + ); }); describe("compiled JSON parser and logger success seam", () => { @@ -159,3 +294,78 @@ describe("compiled JSON parser and logger success seam", () => { }, ); }); + +describe("compiled CLI parser with relative brace and bracket paths", () => { + for (const filename of ["[input].json", "{capture}.json"]) + cliTest( + `reads the selected relative file ${filename}`, + async ({ processes }) => { + const root = await createTestTempDirectory("rea-cli-json-path-"); + const value = { selected: filename }; + await writeFile(join(root, filename), JSON.stringify(value)); + const moduleUrl = new URL( + "../../../dist/cliJsonInput.js", + import.meta.url, + ).href; + const result = await processes.run( + process.execPath, + [ + "--input-type=module", + "-e", + `import { parseCliJsonInput } from ${JSON.stringify(moduleUrl)}; + console.log(JSON.stringify(await parseCliJsonInput(process.argv[1], "test-input")));`, + filename, + ], + { + cwd: root, + env: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }, + ); + + expect(result.stdout).toBe(`${JSON.stringify({ ok: true, value })}\n`); + expect(result.stderr).toBe(""); + expect(result.exitCode).toBe(0); + }, + ); + + for (const filename of ["[missing].json", "{missing}.json"]) + cliTest( + `keeps missing relative path diagnostics for ${filename}`, + async ({ processes }) => { + const root = await createTestTempDirectory("rea-cli-json-missing-"); + const moduleUrl = new URL( + "../../../dist/cliJsonInput.js", + import.meta.url, + ).href; + const result = await processes.run( + process.execPath, + [ + "--input-type=module", + "-e", + `import { parseCliJsonInput } from ${JSON.stringify(moduleUrl)}; + console.log(JSON.stringify(await parseCliJsonInput(process.argv[1], "test-input")));`, + filename, + ], + { + cwd: root, + env: { + HOME: root, + XDG_CONFIG_HOME: root, + XDG_CACHE_HOME: root, + }, + }, + ); + + expect(JSON.parse(result.stdout.trim())).toMatchObject({ + ok: false, + error: { input_path: filename, input_reason: "read-failed" }, + }); + expect(result.stderr).toBe(""); + expect(result.exitCode).toBe(0); + }, + ); +}); diff --git a/tests/boundary/cli/jsonOutput.test.ts b/tests/boundary/cli/jsonOutput.test.ts new file mode 100644 index 000000000..7c1ff4ddd --- /dev/null +++ b/tests/boundary/cli/jsonOutput.test.ts @@ -0,0 +1,170 @@ +import { Writable } from "node:stream"; + +import { describe, expect, it } from "vitest"; + +import { writeJsonOutput } from "../../../src/cli/jsonOutput.js"; +import { createStreamedCliJsonOutput } from "../../../src/cli/streamedJsonOutput.js"; + +const collect = async ( + value: unknown, + format: "json" | "jsonl" = "json", +): Promise => { + const chunks: Buffer[] = []; + const destination = new Writable({ + write(chunk: unknown, _encoding, callback) { + if (!Buffer.isBuffer(chunk)) { + callback(new TypeError("Expected a UTF-8 output buffer")); + return; + } + chunks.push(chunk); + callback(); + }, + }); + await writeJsonOutput(value, destination, format); + return Buffer.concat(chunks).toString("utf8"); +}; + +describe("incremental CLI JSON", () => { + it.each([ + null, + true, + -0, + 1e-7, + "", + { empty: [], object: {}, nested: [{ value: null }] }, + { 'quoted"key': '\u0000\b\t\n\f\r\\"', unicode: "雪𝟠\ud800\udfff" }, + { text: `${"x".repeat(8191)}𝟠${"y".repeat(8191)}\ud800` }, + ])("matches native JSON encoding for %j", async (value) => { + expect(await collect(value)).toBe(`${JSON.stringify(value, null, 2)}\n`); + }); + + it("preserves shared objects without interpreting them as cycles", async () => { + const shared = { value: "shared" }; + const value = { copies: [shared, shared] }; + expect(await collect(value)).toBe(`${JSON.stringify(value, null, 2)}\n`); + }); + + it("writes compact JSONL records and preserves an empty record set", async () => { + const value = [{ text: "first\nrecord" }, { text: "雪𝟠" }]; + expect(await collect(value, "jsonl")).toBe( + `${value.map((item) => JSON.stringify(item)).join("\n")}\n`, + ); + expect(await collect([], "jsonl")).toBe(""); + }); + + it("bounds escaped leaf writes and waits for a slow destination", async () => { + const leaf = `\u0000雪𝟠`.repeat(32 * 1024); + const chunks: Buffer[] = []; + let largestChunk = 0; + const destination = new Writable({ + highWaterMark: 1, + write(chunk: unknown, _encoding, callback) { + if (!Buffer.isBuffer(chunk)) { + callback(new TypeError("Expected UTF-8 bytes")); + return; + } + largestChunk = Math.max(largestChunk, chunk.length); + setTimeout(() => { + chunks.push(chunk); + callback(); + }, 1); + }, + }); + await writeJsonOutput({ leaf }, destination); + expect(largestChunk).toBeLessThan(Buffer.byteLength(leaf)); + const actual = Buffer.concat(chunks).toString("utf8"); + const expected = `${JSON.stringify({ leaf }, null, 2)}\n`; + expect(actual.length).toBe(expected.length); + expect(actual === expected).toBe(true); + expect(destination.writableEnded).toBe(false); + }); + + it("waits for the final write even when it fits the destination buffer", async () => { + let completed = false; + const destination = new Writable({ + write(_chunk, _encoding, callback) { + setTimeout(() => { + completed = true; + callback(); + }, 5); + }, + }); + await writeJsonOutput({ result: "small" }, destination); + expect(completed).toBe(true); + }); + + it.each([false, true])( + "propagates a failed destination (deferred: %s)", + async (deferred) => { + const cause = new Error("Fixture destination failed"); + const destination = new Writable({ + write(_chunk, _encoding, callback) { + if (deferred) setTimeout(() => callback(cause), 5); + else callback(cause); + }, + }); + await expect( + writeJsonOutput({ result: "small" }, destination), + ).rejects.toBe(cause); + }, + ); + + it("leaves a reusable destination open without adding error listeners", async () => { + const destination = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, + }); + const listeners = destination.listenerCount("error"); + await writeJsonOutput({ result: "first" }, destination); + await writeJsonOutput({ result: "second" }, destination); + expect(destination.listenerCount("error")).toBe(listeners); + expect(destination.writableEnded).toBe(false); + }); + + it("rejects a cycle at the serialization boundary", async () => { + const cycle: Record = {}; + cycle.self = cycle; + await expect(collect(cycle)).rejects.toThrow("circular reference"); + }); + + it.each([undefined, Number.NaN, Number.POSITIVE_INFINITY, 1n, new Date(0)])( + "rejects non-JSON input %s", + async (value) => { + await expect(collect(value)).rejects.toThrow("ordinary JSON"); + }, + ); +}); + +describe("streamed CLI result surface", () => { + it.each([ + [], + ["--format", "md"], + ["--json", "--token-count"], + ["--json", "--token-limit", "5"], + ["--json", "--format", "invalid"], + ])("delegates unsupported output controls %j", (...arguments_) => { + expect(createStreamedCliJsonOutput(arguments_, new Writable())).toBe( + undefined, + ); + }); + + it("keeps ownership after a failed write so no second document is emitted", async () => { + const cause = new Error("Fixture destination failed"); + const destination = new Writable({ + write(_chunk, _encoding, callback) { + callback(cause); + }, + }); + const output = createStreamedCliJsonOutput(["--json"], destination); + if (output === undefined) throw new Error("Missing JSON output surface"); + await expect( + output.write( + { result: "small" }, + { command: "fixture", duration: "1ms", format: "json" }, + ), + ).rejects.toBe(cause); + expect(output.handled).toBe(true); + expect(output.failed).toBe(true); + }); +}); diff --git a/tests/boundary/cli/nativeUiScenarioInputCli.test.ts b/tests/boundary/cli/nativeUiScenarioInputCli.test.ts new file mode 100644 index 000000000..766b77a79 --- /dev/null +++ b/tests/boundary/cli/nativeUiScenarioInputCli.test.ts @@ -0,0 +1,74 @@ +import { describe, expect } from "vitest"; + +import { cliTest } from "../../support/cli/cliFixture.js"; +import type { TestCli } from "../../support/cli/cliFixture.js"; + +const runCapture = async (cli: TestCli, steps: string | undefined) => { + const arguments_ = [ + "capture-native-ui-scenario", + "/provider-must-not-be-invoked", + "--pid", + "1", + "--window-id", + "1", + ]; + if (steps !== undefined) arguments_.push("--steps", steps); + arguments_.push("--json"); + return cli.run({ + arguments: arguments_, + environment: { REA_LOG_LEVEL: "silent" }, + }); +}; + +describe("native UI scenario CLI input", () => { + cliTest( + "rejects omitted steps under the shared scenario contract", + async ({ cli }) => { + const result = await runCapture(cli, undefined); + + expect(result.exitCode).not.toBe(0); + expect(result.json).toMatchObject({ + code: "invalid_request", + details: { + operation: "capture_native_ui_scenario", + issues: [{ path: ["steps"], reason: "missing_argument" }], + }, + }); + }, + ); + + cliTest("rejects malformed JSON as invalid input", async ({ cli }) => { + const result = await runCapture(cli, "["); + + expect(result.exitCode).not.toBe(0); + expect(result.json).toMatchObject({ + code: "invalid_request", + details: { + operation: "capture_native_ui_scenario", + issues: [ + { + path: ["steps"], + reason: "invalid_format", + expected: "JSON", + }, + ], + }, + }); + }); + + cliTest( + "requires at least one schema-valid scenario step", + async ({ cli }) => { + const result = await runCapture(cli, "[]"); + + expect(result.exitCode).not.toBe(0); + expect(result.json).toMatchObject({ + code: "invalid_request", + details: { + operation: "capture_native_ui_scenario", + issues: [{ path: ["steps"], reason: "out_of_range", minimum: 1 }], + }, + }); + }, + ); +}); diff --git a/tests/boundary/cli/processCli.test.ts b/tests/boundary/cli/processCli.test.ts index a35b92aeb..fd796634e 100644 --- a/tests/boundary/cli/processCli.test.ts +++ b/tests/boundary/cli/processCli.test.ts @@ -13,9 +13,9 @@ import { captureProcessScenarioFile, compareProcessEvidenceFiles, projectProcessCliError, -} from "../../../src/application/ProcessCli.js"; +} from "../../../src/application/process/ProcessCli.js"; import { createEvidence } from "../../../src/domain/evidence.js"; -import { PROCESS_PROVIDER } from "../../../src/application/ProcessEvidence.js"; +import { PROCESS_PROVIDER } from "../../../src/application/process/ProcessEvidence.js"; const roots: string[] = []; const execFileAsync = promisify(execFile); @@ -120,8 +120,17 @@ describe("process CLI errors", () => { const root = await fixture(); const malformed = join(root, "malformed.json"); const invalid = join(root, "invalid.json"); + const invalidUtf8 = join(root, "invalid-utf8.json"); await writeFile(malformed, "not-json"); await writeFile(invalid, "{}"); + await writeFile( + invalidUtf8, + Buffer.concat([ + Buffer.from('{"executable":"'), + Buffer.from([0xff]), + Buffer.from('"}'), + ]), + ); expect( await captureProcessScenarioFile(join(root, "missing.json")), @@ -137,6 +146,12 @@ describe("process CLI errors", () => { message: "Process input file is not valid JSON. Repair the file, then try again.", }); + expect(await captureProcessScenarioFile(invalidUtf8)).toEqual({ + error: "Process command failed", + category: "invalid_input", + message: + "Process input file is not valid JSON. Repair the file, then try again.", + }); expect(await captureProcessScenarioFile(invalid)).toEqual({ error: "Process command failed", code: "invalid_request", @@ -153,6 +168,66 @@ describe("process CLI errors", () => { }); }); + it("rejects NUL arguments before process launch", async () => { + const root = await fixture(); + const scenario = join(root, "nul-argument.json"); + await writeFile( + scenario, + JSON.stringify({ executable: process.execPath, arguments: ["\0"] }), + ); + + expect(await captureProcessScenarioFile(scenario)).toMatchObject({ + error: "Process command failed", + code: "invalid_request", + category: "invalid_input", + details: { + operation: "capture_process_scenario", + issues: [ + { + path: ["arguments", 0], + reason: "invalid_format", + expected: "regex", + message: + "Values passed to operating-system APIs cannot contain NUL", + }, + ], + }, + }); + }); +}); + +describe("process CLI environment key diagnostics", () => { + it("reports the reserved process environment key constraint", async () => { + const root = await fixture(); + const scenario = join(root, "reserved-environment.json"); + await writeFile( + scenario, + JSON.stringify({ + executable: process.execPath, + environment: { REA_PROCESS_RUN_ID: "caller-value" }, + }), + ); + + expect(await captureProcessScenarioFile(scenario)).toMatchObject({ + error: "Process command failed", + code: "invalid_request", + category: "invalid_input", + details: { + operation: "capture_process_scenario", + issues: [ + { + path: ["environment", "REA_PROCESS_RUN_ID"], + reason: "invalid_format", + expected: "regex", + message: "REA_PROCESS_RUN_ID is reserved by the process adapter", + }, + ], + }, + }); + }); +}); + +describe("process CLI evidence validation", () => { it("captures the minimal executable-and-arguments scenario", async () => { const root = await fixture(); const scenario = join(root, "scenario.json"); diff --git a/tests/boundary/cli/productCatalog.test.ts b/tests/boundary/cli/productCatalog.test.ts index f3a483ecf..3e8c98b5d 100644 --- a/tests/boundary/cli/productCatalog.test.ts +++ b/tests/boundary/cli/productCatalog.test.ts @@ -19,6 +19,10 @@ import { MANAGED_WORKFLOW_PROVIDER, } from "../../../src/application/InvestigationProviders.js"; import { CDP_BROWSER_PROVIDER_IDENTITY } from "../../../src/browser/CdpBrowserProvider.js"; +import { + HAR_CAPTURE_PROVIDER_IDENTITY, + MITMPROXY_CAPTURE_PROVIDER_IDENTITY, +} from "../../../src/browser/history/CaptureRelease.js"; import { CDP_ELECTRON_PROVIDER_IDENTITY } from "../../../src/browser/CdpElectronProvider.js"; import { PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY } from "../../../src/browser/PlaywrightElectronActiveProvider.js"; import { PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY } from "../../../src/browser/PlaywrightBrowserScenarioProvider.js"; @@ -106,6 +110,8 @@ describe("canonical product catalog", () => { BINWALK_PROVIDER_IDENTITY, UNBLOB_PROVIDER_IDENTITY, WAKARU_PROVIDER_IDENTITY, + HAR_CAPTURE_PROVIDER_IDENTITY, + MITMPROXY_CAPTURE_PROVIDER_IDENTITY, GHIDRA_PROVIDER_IDENTITY, IDA_PROVIDER_IDENTITY, NATIVE_MACOS_PROVIDER_IDENTITY, @@ -286,6 +292,8 @@ describe("canonical product catalog drift", () => { true, ); expect(issues.some((issue) => issue.includes("Future Client"))).toBe(true); + expect(issues).toContain("docs/installation.md: missing Future Client"); + expect(issues).not.toContain("README.md: missing Future Client"); }); it("changes the provider projection digest when provider facts drift", async () => { diff --git a/tests/boundary/cli/scenarioCliInputDiagnostics.test.ts b/tests/boundary/cli/scenarioCliInputDiagnostics.test.ts new file mode 100644 index 000000000..bc98d9d9f --- /dev/null +++ b/tests/boundary/cli/scenarioCliInputDiagnostics.test.ts @@ -0,0 +1,64 @@ +import { describe, expect } from "vitest"; + +import { cliTest } from "../../support/cli/cliFixture.js"; + +describe("scenario CLI schema diagnostics", () => { + cliTest("reports browser scenario schema paths", async ({ cli }) => { + const result = await cli.run({ + arguments: ["capture-browser-scenario", "{}", "--json"], + environment: { REA_LOG_LEVEL: "silent" }, + }); + + expect(result.exitCode).not.toBe(0); + expect(result.json).toMatchObject({ + code: "invalid_request", + details: { + operation: "capture_browser_scenario", + issues: expect.arrayContaining([ + { + path: ["browser"], + reason: "missing_argument", + expected: "object", + }, + { + path: ["start_url"], + reason: "missing_argument", + expected: "object", + }, + { + path: ["actions"], + reason: "missing_argument", + expected: "array", + }, + ]), + }, + }); + }); + + cliTest("reports Electron scenario schema paths", async ({ cli }) => { + const result = await cli.run({ + arguments: ["capture-electron-scenario", "{}", "--json"], + environment: { REA_LOG_LEVEL: "silent" }, + }); + + expect(result.exitCode).not.toBe(0); + expect(result.json).toMatchObject({ + code: "invalid_request", + details: { + operation: "capture_electron_scenario", + issues: expect.arrayContaining([ + { + path: ["executable_path"], + reason: "missing_argument", + expected: "string", + }, + { + path: ["application_path"], + reason: "missing_argument", + expected: "string", + }, + ]), + }, + }); + }); +}); diff --git a/tests/boundary/filesystem/artifactComparisonSummary.test.ts b/tests/boundary/filesystem/artifactComparisonSummary.test.ts index 63266bc0b..7879f96aa 100644 --- a/tests/boundary/filesystem/artifactComparisonSummary.test.ts +++ b/tests/boundary/filesystem/artifactComparisonSummary.test.ts @@ -3,7 +3,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { inventoryArtifact } from "../../../src/application/ArtifactInventory.js"; +import { inventoryArtifact } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { compareArtifacts } from "../../../src/domain/artifactComparison.js"; import { createEvidence } from "../../../src/domain/evidence.js"; import { jsonValueSchema } from "../../../src/domain/jsonValue.js"; @@ -31,10 +31,7 @@ const observe = async (path: string) => { describe("artifact comparison summary", () => { it.each([ { removed: 1, added: 1 }, - { removed: 1, added: 2 }, - { removed: 2, added: 1 }, { removed: 0, added: 1 }, - { removed: 1, added: 0 }, { removed: 0, added: 0 }, ])( "counts unchanged paths with $removed removals and $added additions", @@ -74,15 +71,6 @@ describe("artifact comparison summary", () => { contradiction: 0, }); expect(comparison.changes).toHaveLength(removed + added + changed); - expect(comparison.summary.unchanged + comparison.changes.length).toBe( - 3 + removed + added, - ); - expect( - comparison.changes.map(({ logical_path }) => logical_path), - ).not.toContain("same.txt"); - expect( - comparison.changes.map(({ logical_path }) => logical_path), - ).not.toContain("duplicate.txt"); }, ); }); diff --git a/tests/boundary/filesystem/artifactDirectoryIdentity.test.ts b/tests/boundary/filesystem/artifactDirectoryIdentity.test.ts index bcd1542c4..e87794869 100644 --- a/tests/boundary/filesystem/artifactDirectoryIdentity.test.ts +++ b/tests/boundary/filesystem/artifactDirectoryIdentity.test.ts @@ -5,7 +5,7 @@ import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js"; import { describe, expect, it } from "vitest"; import { canonicalDigest } from "../../../src/domain/comparisonSemantics.js"; -import { inventoryArtifact } from "../../../src/application/ArtifactInventory.js"; +import { inventoryArtifact } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { compareArtifacts } from "../../../src/domain/artifactComparison.js"; import type { ArtifactInventoryResult } from "../../../src/domain/artifactGraph.js"; import { createEvidence } from "../../../src/domain/evidence.js"; diff --git a/tests/boundary/filesystem/artifactInventoryEntryOrder.test.ts b/tests/boundary/filesystem/artifactInventoryEntryOrder.test.ts index d4532b659..6d106e335 100644 --- a/tests/boundary/filesystem/artifactInventoryEntryOrder.test.ts +++ b/tests/boundary/filesystem/artifactInventoryEntryOrder.test.ts @@ -4,7 +4,7 @@ import { join } from "node:path"; import { createPackage } from "@electron/asar"; import { describe, expect, it } from "vitest"; -import { inventoryArtifact } from "../../../src/application/ArtifactInventory.js"; +import { inventoryArtifact } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { artifactOccurrenceAt, artifactParentPaths, @@ -14,10 +14,6 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; const permutations = [ ["pkg/", "pkg/sub/", "pkg/sub/data.txt"], - ["pkg/", "pkg/sub/data.txt", "pkg/sub/"], - ["pkg/sub/", "pkg/", "pkg/sub/data.txt"], - ["pkg/sub/", "pkg/sub/data.txt", "pkg/"], - ["pkg/sub/data.txt", "pkg/", "pkg/sub/"], ["pkg/sub/data.txt", "pkg/sub/", "pkg/"], ]; const expectedParents = { diff --git a/tests/boundary/filesystem/artifactInventorySnapshot.test.ts b/tests/boundary/filesystem/artifactInventorySnapshot.test.ts index 5feec925f..bd0201d67 100644 --- a/tests/boundary/filesystem/artifactInventorySnapshot.test.ts +++ b/tests/boundary/filesystem/artifactInventorySnapshot.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; -import { scanArtifactInventory } from "../../../src/application/ArtifactInventory.js"; +import { scanArtifactInventory } from "../../../src/artifacts/inventory/ArtifactInventory.js"; describe("artifact inventory snapshot", () => { it("retains the complete scan after the source directory changes", async () => { diff --git a/tests/boundary/filesystem/artifactMzInventory.test.ts b/tests/boundary/filesystem/artifactMzInventory.test.ts index fd9884f95..c3fd44005 100644 --- a/tests/boundary/filesystem/artifactMzInventory.test.ts +++ b/tests/boundary/filesystem/artifactMzInventory.test.ts @@ -3,15 +3,12 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { classifyArtifactContent } from "../../../src/application/ArtifactGraphConstruction.js"; -import { classifyRoot } from "../../../src/application/ArtifactInventory/classify.js"; +import { classifyArtifactContent } from "../../../src/artifacts/inventory/ArtifactGraphConstruction.js"; +import { classifyRoot } from "../../../src/artifacts/inventory/classify.js"; import { ARTIFACT_CLASSIFICATION_PREFIX_BYTES } from "../../../src/artifacts/ArtifactHash.js"; -import { scanCanonicalArtifactInventory } from "../../../src/application/ArtifactInventory/scanCanonical.js"; +import { scanCanonicalArtifactInventory } from "../../../src/artifacts/inventory/scanCanonical.js"; import { targetFormatSchema } from "../../../src/contracts/toolOutputSchemaPrimitives.js"; import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js"; -import { createEvidence } from "../../../src/domain/evidence.js"; -import { jsonValueSchema } from "../../../src/domain/jsonValue.js"; -import { identifyRuntimes } from "../../../src/domain/runtimeIdentification.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; describe("MZ artifact classification", () => { @@ -62,7 +59,7 @@ describe("MZ artifact classification", () => { expect(targetFormatSchema.parse("dos-mz")).toBe("dos-mz"); }); - it("keeps standalone and embedded DOS identities through inventory and runtime evidence", async () => { + it("keeps standalone and embedded DOS identities through inventory", async () => { const directory = await createTestTempDirectory("rea-mz-inventory-"); const dosPath = join(directory, "LEGACY.EXE"); const pePath = join(directory, "NATIVE.EXE"); @@ -88,30 +85,6 @@ describe("MZ artifact classification", () => { expect(inventory.nodes.map(({ format }) => format)).toEqual( expect.arrayContaining(["dos-mz", "pe"]), ); - const evidence = createEvidence( - { - path: tree, - sha256: inventory.manifest.root_sha256, - format: "directory", - }, - { id: "inventory-fixture", name: "Inventory fixture", version: null }, - { - operation: "inventory_artifact", - parameters: {}, - result: jsonValueSchema.parse(inventory), - }, - ); - const runtimes = identifyRuntimes({ inventory_evidence: [evidence] }); - expect(runtimes.runtimes).toContainEqual( - expect.objectContaining({ - family: "native", - inspection: "provider-selection-required", - observations: expect.arrayContaining([ - expect.objectContaining({ path: "LEGACY.EXE", format: "dos-mz" }), - expect.objectContaining({ path: "NATIVE.EXE", format: "pe" }), - ]), - }), - ); }); }); diff --git a/tests/boundary/filesystem/artifactZipIntegrity.test.ts b/tests/boundary/filesystem/artifactZipIntegrity.test.ts new file mode 100644 index 000000000..10b221819 --- /dev/null +++ b/tests/boundary/filesystem/artifactZipIntegrity.test.ts @@ -0,0 +1,113 @@ +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js"; +import { expect, it } from "vitest"; + +import { writeOrderedZip } from "../../fixtures/artifactEntryOrder.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js"; +import { ArtifactProvider } from "../../../src/artifacts/ArtifactProvider.js"; +import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js"; + +it("rejects overlapping ZIP member data ranges through artifact inventory", async () => { + const root = await createTestTempDirectory("rea-zip-overlap-"); + const path = join(root, "overlap.zip"); + await writeOrderedZip(path, ["a.txt", "b.txt"]); + const bytes = await readFile(path); + const centralDirectorySignature = Buffer.from([0x50, 0x4b, 0x01, 0x02]); + const first = bytes.indexOf(centralDirectorySignature); + const second = bytes.indexOf(centralDirectorySignature, first + 4); + if (first < 0 || second <= first) + throw new Error("Expected two ZIP central-directory entries"); + + const firstLocalOffset = bytes.readUInt32LE(first + 42); + const secondLocalOffset = bytes.readUInt32LE(second + 42); + expect(secondLocalOffset).not.toBe(firstLocalOffset); + bytes.writeUInt32LE(firstLocalOffset, second + 42); + await writeFile(path, bytes); + + const target = await parseBinaryTarget(path); + if (!target.ok) throw target.error; + const result = await new ArtifactProvider() + .createClient(target.value) + .execute("inventory_artifact", {}); + + expect(result).toMatchObject({ + ok: false, + error: { _tag: "ArtifactOperationError", reason: "io" }, + }); +}); + +it("preserves encrypted-member and corrupt-CRC observations", async () => { + const root = await createTestTempDirectory("rea-zip-integrity-"); + const encryptedPath = join(root, "encrypted.zip"); + const encryptedWriter = new ZipWriter(new Uint8ArrayWriter()); + await encryptedWriter.add("secret.txt", new TextReader("secret bytes"), { + password: "fixture-password", + }); + await writeFile(encryptedPath, await encryptedWriter.close()); + + const encryptedTarget = await parseBinaryTarget(encryptedPath); + if (!encryptedTarget.ok) throw encryptedTarget.error; + const encryptedResult = await new ArtifactProvider() + .createClient(encryptedTarget.value) + .execute("inventory_artifact", {}); + if (!encryptedResult.ok) throw encryptedResult.error; + const encryptedInventory = artifactInventoryResultSchema.parse( + encryptedResult.value.result, + ); + expect(encryptedInventory.occurrences).toContainEqual( + expect.objectContaining({ + logical_path: "secret.txt", + encrypted: true, + hash_status: "unavailable", + }), + ); + + const corruptPath = join(root, "corrupt-crc.zip"); + const corruptWriter = new ZipWriter(new Uint8ArrayWriter()); + await corruptWriter.add("data.txt", new TextReader("evidence\n")); + await writeFile(corruptPath, await corruptWriter.close()); + const bytes = await readFile(corruptPath); + const localFileHeaderSignature = Buffer.from([0x50, 0x4b, 0x03, 0x04]); + const localHeader = bytes.indexOf(localFileHeaderSignature); + if (localHeader < 0) throw new Error("Expected a ZIP local-file header"); + const dataOffset = + localHeader + + 30 + + bytes.readUInt16LE(localHeader + 26) + + bytes.readUInt16LE(localHeader + 28); + const firstByte = bytes[dataOffset]; + if (firstByte === undefined) throw new Error("Expected ZIP member bytes"); + bytes[dataOffset] = firstByte ^ 1; + await writeFile(corruptPath, bytes); + + const corruptTarget = await parseBinaryTarget(corruptPath); + if (!corruptTarget.ok) throw corruptTarget.error; + const corruptResult = await new ArtifactProvider() + .createClient(corruptTarget.value) + .execute("inventory_artifact", {}); + expect(corruptResult).toMatchObject({ + ok: false, + error: { _tag: "ArtifactOperationError", reason: "io" }, + }); +}); + +it("rejects a truncated ZIP through artifact inventory", async () => { + const root = await createTestTempDirectory("rea-zip-malformed-"); + const path = join(root, "truncated.zip"); + await writeFile(path, Buffer.from([0x50, 0x4b, 0x03, 0x04])); + + const target = await parseBinaryTarget(path); + if (!target.ok) throw target.error; + const result = await new ArtifactProvider() + .createClient(target.value) + .execute("inventory_artifact", {}); + + expect(result).toMatchObject({ + ok: false, + error: { _tag: "ArtifactOperationError", reason: "io" }, + }); +}); diff --git a/tests/boundary/filesystem/asarInventoryContainment.test.ts b/tests/boundary/filesystem/asarInventoryContainment.test.ts index 703fa9f8a..6e1f02133 100644 --- a/tests/boundary/filesystem/asarInventoryContainment.test.ts +++ b/tests/boundary/filesystem/asarInventoryContainment.test.ts @@ -11,7 +11,7 @@ import { } from "@zip.js/zip.js"; import { describe, expect, it } from "vitest"; -import { inventoryArtifact } from "../../../src/application/ArtifactInventory.js"; +import { inventoryArtifact } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { artifactOccurrenceAt, artifactParentPaths, diff --git a/tests/boundary/filesystem/asarInventoryFreshness.test.ts b/tests/boundary/filesystem/asarInventoryFreshness.test.ts index 4d0932935..91cdc81b6 100644 --- a/tests/boundary/filesystem/asarInventoryFreshness.test.ts +++ b/tests/boundary/filesystem/asarInventoryFreshness.test.ts @@ -5,7 +5,7 @@ import { buffer } from "node:stream/consumers"; import { createPackage, listPackage, uncache } from "@electron/asar"; import { describe, expect, it } from "vitest"; -import { inventoryArtifact } from "../../../src/application/ArtifactInventory.js"; +import { inventoryArtifact } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; diff --git a/tests/boundary/filesystem/asarStreamingAllocation.test.ts b/tests/boundary/filesystem/asarStreamingAllocation.test.ts new file mode 100644 index 000000000..630091584 --- /dev/null +++ b/tests/boundary/filesystem/asarStreamingAllocation.test.ts @@ -0,0 +1,474 @@ +import { createHash } from "node:crypto"; +import { fstatSync, type Stats } from "node:fs"; +import { + open as openFile, + lstat, + mkdir, + rm, + readdir, + writeFile, + truncate, +} from "node:fs/promises"; +import { join } from "node:path"; +import { Readable } from "node:stream"; +import { buffer } from "node:stream/consumers"; +import { createPackageWithOptions } from "@electron/asar"; +import { describe, expect, it } from "vitest"; +import { scanArtifactInventory } from "../../../src/artifacts/inventory/ArtifactInventory.js"; +import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js"; +import { + closeAsarHandle, + readValidatedAsarEntry, +} from "../../../src/artifacts/AsarEntryStream.js"; +import { hashReadable } from "../../../src/artifacts/ArtifactHash.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +const memberBytes = 64 * 1024 * 1024; +const expectedSha256 = (): string => { + const hash = createHash("sha256"); + const zeros = Buffer.alloc(64 * 1024); + for (let written = 0; written < memberBytes; written += zeros.length) + hash.update(zeros); + return hash.digest("hex"); +}; + +const descriptorDirectory = + process.platform === "darwin" ? "/dev/fd" : "/proc/self/fd"; + +const matchingDescriptorCount = async (identity: Stats): Promise => { + const descriptors = await readdir(descriptorDirectory); + return descriptors.filter((descriptor) => { + if (!/^\d+$/u.test(descriptor)) return false; + try { + const observed = fstatSync(Number(descriptor)); + return observed.dev === identity.dev && observed.ino === identity.ino; + } catch { + return false; + } + }).length; +}; + +const waitForNoMatchingDescriptor = async (identity: Stats): Promise => { + for (let attempt = 0; attempt < 100; attempt += 1) { + if ((await matchingDescriptorCount(identity)) === 0) return; + await new Promise((resolve) => setTimeout(resolve, 10)); + } + expect(await matchingDescriptorCount(identity)).toBe(0); +}; + +describe("ASAR entry streaming", () => { + it.skipIf(process.platform === "win32")( + "closes a packed member handle when its unopened output stream is destroyed", + async () => { + const root = await createTestTempDirectory( + "rea-asar-destroy-before-read-", + ); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + await mkdir(source); + await writeFile(join(source, "small.js"), "module.exports = 1;\n"); + await createPackageWithOptions(source, archive, {}); + + const identity = await lstat(archive); + const probe = await openFile(archive, "r"); + expect(await matchingDescriptorCount(identity)).toBeGreaterThan(0); + await probe.close(); + await waitForNoMatchingDescriptor(identity); + const reader = new AsarArtifactReader(archive); + + try { + const entries = []; + for await (const entry of reader.entries()) { + entries.push(entry); + } + const entry = entries.find(({ path }) => path === "small.js"); + if (entry === undefined) throw new Error("Expected packed ASAR member"); + expect(await matchingDescriptorCount(identity)).toBe(0); + const output = await reader.open(entry); + expect(await matchingDescriptorCount(identity)).toBeGreaterThan(0); + const closed = new Promise((resolve) => + output.once("close", resolve), + ); + output.destroy(); + await closed; + await waitForNoMatchingDescriptor(identity); + } finally { + await reader.close(); + } + }, + ); + + it.skipIf(process.platform === "win32")( + "closes a packed member handle when cancellation arrives during open", + async () => { + const root = await createTestTempDirectory("rea-asar-abort-during-stat-"); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + await mkdir(source); + await writeFile(join(source, "small.js"), "module.exports = 1;\n"); + await createPackageWithOptions(source, archive, {}); + + const identity = await lstat(archive); + const reader = new AsarArtifactReader(archive); + + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + const entry = entries.find(({ path }) => path === "small.js"); + if (entry === undefined) throw new Error("Expected packed ASAR member"); + const controller = new AbortController(); + const opening = reader.open(entry, controller.signal); + controller.abort(); + await expect(opening).rejects.toMatchObject({ reason: "cancelled" }); + await waitForNoMatchingDescriptor(identity); + } finally { + await reader.close(); + } + }, + ); + + it("classifies a missing packed member container as an I/O failure", async () => { + const root = await createTestTempDirectory("rea-asar-missing-member-"); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + await mkdir(source); + await writeFile(join(source, "small.js"), "module.exports = 1;\n"); + await createPackageWithOptions(source, archive, {}); + + const reader = new AsarArtifactReader(archive); + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + const entry = entries.find(({ path }) => path === "small.js"); + if (entry === undefined) throw new Error("Expected packed ASAR member"); + await rm(archive); + await expect(reader.open(entry)).rejects.toMatchObject({ + reason: "io", + cause: { code: "ENOENT" }, + }); + } finally { + await reader.close(); + } + }); +}); + +describe("ASAR entry producer identity", () => { + it.each([false, true] as const)( + "rejects a copied entry with a changed unpacked flag for unpacked=%s", + async (unpacked) => { + const root = await createTestTempDirectory("rea-asar-entry-identity-"); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + await mkdir(source); + await writeFile(join(source, "small.js"), "module.exports = 1;\n"); + await createPackageWithOptions( + source, + archive, + unpacked ? { unpack: "small.js" } : {}, + ); + const selectedPath = unpacked + ? join(`${archive}.unpacked`, "small.js") + : archive; + const identity = await lstat(selectedPath); + const reader = new AsarArtifactReader(archive); + + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + const entry = entries.find(({ path }) => path === "small.js"); + if (entry === undefined) throw new Error("Expected ASAR file entry"); + expect(entry.unpacked).toBe(unpacked); + expect(await matchingDescriptorCount(identity)).toBe(0); + if (entry.declaredSize === null) + throw new Error("Expected ASAR file size metadata"); + + for (const changedEntry of [ + { ...entry, unpacked: !entry.unpacked }, + { ...entry, path: `stale/${entry.path}` }, + { ...entry, declaredSize: entry.declaredSize + 1 }, + ]) + await expect(reader.open(changedEntry)).rejects.toMatchObject({ + name: "ArtifactReaderFailure", + reason: "integrity", + message: expect.stringContaining( + `ASAR entry metadata changed since inventory: ${entry.path}`, + ), + }); + await waitForNoMatchingDescriptor(identity); + } finally { + await reader.close(); + } + }, + ); + + it.each([ + ["packed to unpacked", false], + ["unpacked to packed", true], + ] as const)( + "rejects a retained %s entry after a new entries pass", + async (_transition, wasUnpacked) => { + const root = await createTestTempDirectory("rea-asar-entry-refresh-"); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + const memberContents = "module.exports = 1;\n"; + await mkdir(source); + await writeFile(join(source, "small.js"), memberContents); + const reader = new AsarArtifactReader(archive); + + try { + await createPackageWithOptions( + source, + archive, + wasUnpacked ? { unpack: "small.js" } : {}, + ); + const firstEntries = []; + for await (const entry of reader.entries()) firstEntries.push(entry); + const retained = firstEntries.find(({ path }) => path === "small.js"); + if (retained === undefined) + throw new Error("Expected first ASAR file entry"); + const copiedRetained = { + ...retained, + limitations: [...retained.limitations], + }; + + await createPackageWithOptions( + source, + archive, + wasUnpacked ? {} : { unpack: "small.js" }, + ); + const currentEntries = []; + for await (const entry of reader.entries()) currentEntries.push(entry); + const current = currentEntries.find(({ path }) => path === "small.js"); + if (current === undefined) + throw new Error("Expected refreshed ASAR file entry"); + expect(current.unpacked).toBe(!wasUnpacked); + + await expect(reader.open(copiedRetained)).rejects.toMatchObject({ + name: "ArtifactReaderFailure", + reason: "integrity", + message: expect.stringContaining( + "ASAR entry metadata changed since inventory", + ), + }); + expect((await buffer(await reader.open(current))).toString()).toBe( + memberContents, + ); + } finally { + await reader.close(); + } + }, + ); +}); + +describe("ASAR read failures", () => { + it.skipIf(process.platform === "win32")( + "checks cancellation after closing an empty member handle", + async () => { + const root = await createTestTempDirectory("rea-asar-empty-close-"); + const archive = join(root, "container.asar"); + await writeFile(archive, "container bytes"); + const identity = await lstat(archive); + const handle = await openFile(archive, "r"); + try { + expect(await matchingDescriptorCount(identity)).toBeGreaterThan(0); + const controller = new AbortController(); + + await expect( + closeAsarHandle(async () => { + await handle.close(); + controller.abort(); + }, controller.signal), + ).rejects.toMatchObject({ reason: "cancelled" }); + await waitForNoMatchingDescriptor(identity); + } finally { + await handle.close().catch(() => undefined); + } + }, + ); + + it.skipIf(process.platform === "win32")( + "closes the opened unpacked file and retains ASAR context when handle stat fails", + async () => { + const root = await createTestTempDirectory("rea-asar-unpacked-stat-"); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + await mkdir(source); + await writeFile(join(source, "small.js"), "module.exports = 1;\n"); + await createPackageWithOptions(source, archive, { unpack: "small.js" }); + const unpackedPath = join(`${archive}.unpacked`, "small.js"); + const identity = await lstat(unpackedPath); + const ioError = Object.assign(new Error("device stat failed"), { + code: "EIO", + errno: -5, + syscall: "fstat", + }); + let closeCount = 0; + const reader = new AsarArtifactReader(archive, async (path, flags) => { + const handle = await openFile(path, flags); + expect(await matchingDescriptorCount(identity)).toBeGreaterThan(0); + let statCount = 0; + return { + async stat() { + statCount += 1; + if (statCount === 2) throw ioError; + return handle.stat(); + }, + async close() { + closeCount += 1; + await handle.close(); + }, + createReadStream: (options) => handle.createReadStream(options), + }; + }); + + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + const entry = entries.find(({ path }) => path === "small.js"); + if (entry === undefined) + throw new Error("Expected unpacked ASAR member"); + + await expect(reader.open(entry)).rejects.toMatchObject({ + name: "ArtifactReaderFailure", + reason: "io", + message: expect.stringContaining( + `Could not read ${entry.path} ASAR at ${archive}: device stat failed`, + ), + cause: { code: "EIO", errno: -5, syscall: "fstat" }, + }); + expect(closeCount).toBe(1); + await waitForNoMatchingDescriptor(identity); + } finally { + await reader.close(); + } + }, + ); + + it.each(["missing", "replaced"] as const)( + "validates the container before returning a zero-length member when it is %s", + async (change) => { + const root = await createTestTempDirectory("rea-asar-empty-member-"); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + await mkdir(source); + await writeFile(join(source, "empty.js"), ""); + await createPackageWithOptions(source, archive, {}); + const reader = new AsarArtifactReader(archive); + + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + const entry = entries.find(({ path }) => path === "empty.js"); + if (entry === undefined) throw new Error("Expected empty ASAR member"); + if (change === "missing") await rm(archive); + else await writeFile(archive, "replacement container"); + + await expect(reader.open(entry)).rejects.toMatchObject( + change === "missing" + ? { reason: "io", cause: { code: "ENOENT" } } + : { reason: "integrity" }, + ); + } finally { + await reader.close(); + } + }, + ); + + it("preserves filesystem errors raised while consuming a member stream", async () => { + const ioError = Object.assign(new Error("device read failed"), { + code: "EIO", + errno: -5, + syscall: "read", + }); + const source = Readable.from( + (async function* () { + yield Buffer.from("partial"); + throw ioError; + })(), + ); + const output = readValidatedAsarEntry( + source, + undefined, + "nested/member.bin", + "/tmp/source.asar", + ); + + await expect(async () => { + for await (const _chunk of output) { + // Consume through the source failure. + } + }).rejects.toMatchObject({ + name: "ArtifactReaderFailure", + reason: "io", + message: expect.stringContaining( + "Could not read nested/member.bin ASAR at /tmp/source.asar", + ), + cause: { code: "EIO", errno: -5, syscall: "read" }, + }); + }); +}); + +describe("ASAR entry streaming", () => { + it("reports the observed digest when an unpacked member changes size", async () => { + const root = await createTestTempDirectory( + "rea-asar-unpacked-size-change-", + ); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + const original = join(source, "small.js"); + const changed = "changed();\n"; + await mkdir(source); + await writeFile(original, "module.exports = 1;\n"); + await createPackageWithOptions(source, archive, { unpack: "small.js" }); + await writeFile(join(`${archive}.unpacked`, "small.js"), changed); + + await expect(scanArtifactInventory(archive)).rejects.toMatchObject({ + reason: "integrity", + details: { + logicalPath: "small.js", + calculatedSha256: createHash("sha256").update(changed).digest("hex"), + unpacked: true, + }, + }); + }); + + it.each([ + ["packed", {}], + ["unpacked", { unpack: "large.bin" }], + ] as const)( + "opens a %s member without allocating its full contents", + async (_kind, options) => { + const root = await createTestTempDirectory("rea-asar-streaming-"); + const source = join(root, "source"); + const archive = join(root, "fixture.asar"); + const member = join(source, "large.bin"); + await mkdir(source); + await writeFile(member, Buffer.alloc(0)); + await truncate(member, memberBytes); + await createPackageWithOptions(source, archive, options); + + const reader = new AsarArtifactReader(archive); + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + const entry = entries.find(({ path }) => path === "large.bin"); + if (entry === undefined) throw new Error("Expected large ASAR member"); + + const before = process.memoryUsage().arrayBuffers; + const stream = await reader.open(entry); + const afterOpen = process.memoryUsage().arrayBuffers; + const observed = await hashReadable(stream); + const allocation = afterOpen - before; + + expect(allocation).toBeLessThan(memberBytes / 2); + expect(observed).toMatchObject({ + sha256: expectedSha256(), + bytes: memberBytes, + }); + } finally { + await reader.close(); + } + }, + 120_000, + ); +}); diff --git a/tests/boundary/filesystem/authorizedArtifactInventory.test.ts b/tests/boundary/filesystem/authorizedArtifactInventory.test.ts deleted file mode 100644 index 2598a83a6..000000000 --- a/tests/boundary/filesystem/authorizedArtifactInventory.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { writeFile } from "node:fs/promises"; -import { join } from "node:path"; - -import { describe, expect, it } from "vitest"; - -import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; - -import { scanAuthorizedArtifactInventory } from "../../../src/application/AuthorizedArtifactInventory.js"; - -describe("authorized artifact inventory", () => { - it("scans a local artifact without configured input roots", async () => { - const root = await createTestTempDirectory("rea-artifact-roots-"); - const path = join(root, "artifact.js"); - await writeFile(path, "export const value = 1;\n"); - const inventory = await scanAuthorizedArtifactInventory(path); - expect(inventory.manifest.root_format).toBe("javascript-bundle"); - }); -}); diff --git a/tests/boundary/filesystem/boundedFileBytes.test.ts b/tests/boundary/filesystem/boundedFileBytes.test.ts index b128804b2..a0c358ae6 100644 --- a/tests/boundary/filesystem/boundedFileBytes.test.ts +++ b/tests/boundary/filesystem/boundedFileBytes.test.ts @@ -7,7 +7,7 @@ import { readBoundedFileBytes } from "../../../src/process/BoundedFileBytes.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; describe("bounded file byte reads", () => { - it.each([0, 1, 65_536, 65_537])( + it.each([0, 65_536])( "accepts a file exactly at the %i byte limit", async (limit) => { const root = await createTestTempDirectory("rea-bounded-bytes-"); diff --git a/tests/boundary/filesystem/cliJsonInput.test.ts b/tests/boundary/filesystem/cliJsonInput.test.ts index 315f3ebf4..406f14b66 100644 --- a/tests/boundary/filesystem/cliJsonInput.test.ts +++ b/tests/boundary/filesystem/cliJsonInput.test.ts @@ -7,13 +7,18 @@ import { parseCliJsonInput } from "../../../src/cliJsonInput.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; describe("CLI JSON input", () => { - it("preserves JSON scalars and distinguishes malformed inline text from files", async () => { - for (const value of [null, false, 0, "text", [], {}]) - expect( - await parseCliJsonInput(JSON.stringify(value), "test-input"), - ).toEqual({ ok: true, value }); - expect(await parseCliJsonInput("{", "test-input")).toMatchObject({ + it("distinguishes malformed inline text from files and preserves bracket-prefixed paths", async () => { + expect(await parseCliJsonInput("[", "test-input")).toMatchObject({ ok: false, + error: { + details: { issues: [{ reason: "invalid_format", expected: "JSON" }] }, + }, + }); + expect(await parseCliJsonInput("[", "test-input")).toMatchObject({ + ok: false, + error: { + details: { issues: [{ reason: "invalid_format", expected: "JSON" }] }, + }, }); const root = await createTestTempDirectory("rea-json-input-"); const path = join(root, "input.json"); @@ -22,6 +27,24 @@ describe("CLI JSON input", () => { ok: true, value: { value: 1 }, }); + expect(await parseCliJsonInput("false", "test-input")).toEqual({ + ok: true, + value: false, + }); + const bracketPath = join(root, "[input].json"); + await writeFile(bracketPath, '["preserved"]'); + expect(await parseCliJsonInput(bracketPath, "test-input")).toEqual({ + ok: true, + value: ["preserved"], + }); + for (const missingPath of [ + join(root, "{capture}.json"), + join(root, "[missing].json"), + ]) + expect(await parseCliJsonInput(missingPath, "test-input")).toMatchObject({ + ok: false, + error: { input_path: missingPath, input_reason: "read-failed" }, + }); expect(await parseCliJsonInput(root, "test-input")).toMatchObject({ ok: false, error: { input_reason: "read-failed" }, diff --git a/tests/boundary/filesystem/clientConfigurationBom.test.ts b/tests/boundary/filesystem/clientConfigurationBom.test.ts index 9ca72642d..73650f971 100644 --- a/tests/boundary/filesystem/clientConfigurationBom.test.ts +++ b/tests/boundary/filesystem/clientConfigurationBom.test.ts @@ -15,7 +15,6 @@ import { inspectClientConfiguration, } from "../../../src/application/SetupClientConfiguration.js"; import { supportedClients } from "../../../src/application/SupportedClients.js"; -import { PRODUCT_IDENTITY } from "../../../src/identity.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; describe.each([ @@ -46,68 +45,55 @@ describe.each([ await mkdir(dirname(client.configPath), { recursive: true }); await writeFile(client.configPath, original); const currentCommandPath = join(home, "rea"); - - for (const command of [ - ["npx", "-y", PRODUCT_IDENTITY.registrationPackageSpecifier, "mcp"], - [currentCommandPath, "mcp"], - ]) { - expect(await inspectClientConfiguration(client, {}, command)).toEqual({ - status: "update", - backupPath: `${client.configPath}.rea.backup`, - }); - expect(await clientConfigurationAligned(client, {}, command)).toBe( - false, - ); - expect(await configureClientConfiguration(client, {}, command)).toEqual( - { - status: "configured", - backupPath: `${client.configPath}.rea.backup`, - }, - ); - const updated = await readFile(client.configPath, "utf8"); - expect( - updated.startsWith(`${bom}// Keep the client preferences.\r\n`), - ).toBe(true); - expect( - parseClientConfiguration(updated, client.format).document, - ).toEqual({ + const command = [currentCommandPath, "mcp"]; + expect(await inspectClientConfiguration(client, {}, command)).toEqual({ + status: "update", + backupPath: `${client.configPath}.rea.backup`, + }); + expect(await clientConfigurationAligned(client, {}, command)).toBe(false); + expect(await configureClientConfiguration(client, {}, command)).toEqual({ + status: "configured", + backupPath: `${client.configPath}.rea.backup`, + }); + const updated = await readFile(client.configPath, "utf8"); + expect( + updated.startsWith(`${bom}// Keep the client preferences.\r\n`), + ).toBe(true); + expect(parseClientConfiguration(updated, client.format).document).toEqual( + { label: "left\uFEFFright", [serversKey]: { other: { command: "other" }, rea: clientRegistrationEntry(client.format, command, {}), }, theme: "dark", - }); - expect(await clientConfigurationAligned(client, {}, command)).toBe( - true, - ); - expect(await inspectClientConfiguration(client, {}, command)).toEqual({ - status: "already_current", - }); - expect( - await readClientRegistrationStatuses(home, currentCommandPath, { - platform: "linux", - environment: {}, - }), - ).toEqual([ - { - client: name, - config_path: client.configPath, - state: "aligned", - command, - remediation: null, - }, - ]); - expect(await configureClientConfiguration(client, {}, command)).toEqual( - { - status: "unchanged", - }, - ); - expect(await readFile(client.configPath)).toEqual(Buffer.from(updated)); - expect(await readFile(`${client.configPath}.rea.backup`)).toEqual( - Buffer.from(original), - ); - } + }, + ); + expect(await clientConfigurationAligned(client, {}, command)).toBe(true); + expect(await inspectClientConfiguration(client, {}, command)).toEqual({ + status: "already_current", + }); + expect( + await readClientRegistrationStatuses(home, currentCommandPath, { + platform: "linux", + environment: {}, + }), + ).toEqual([ + { + client: name, + config_path: client.configPath, + state: "aligned", + command, + remediation: null, + }, + ]); + expect(await configureClientConfiguration(client, {}, command)).toEqual({ + status: "unchanged", + }); + expect(await readFile(client.configPath)).toEqual(Buffer.from(updated)); + expect(await readFile(`${client.configPath}.rea.backup`)).toEqual( + Buffer.from(original), + ); }, ); }); diff --git a/tests/boundary/filesystem/clientConfigurationTransactions.test.ts b/tests/boundary/filesystem/clientConfigurationTransactions.test.ts index efdd91e6f..7f803cad8 100644 --- a/tests/boundary/filesystem/clientConfigurationTransactions.test.ts +++ b/tests/boundary/filesystem/clientConfigurationTransactions.test.ts @@ -34,25 +34,23 @@ describe("client configuration write failures", () => { }); describe("TOML client configuration comparison", () => { - it.each(["nan", "inf", "-inf"])( - "replaces a registration containing %s and preserves unrelated settings", - async (value) => { - const root = await createTestTempDirectory("rea-client-toml-"); - const configPath = join(root, "config.toml"); - const original = `unrelated = ${value}\n[mcp_servers.rea]\ncommand = "old"\nstartup_timeout_sec = ${value}\n`; - await writeFile(configPath, original); - const client = { name: "codex", format: "toml", configPath } as const; - expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({ - status: "configured", - backupPath: `${configPath}.rea.backup`, - }); - expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original); - expect(await readFile(configPath, "utf8")).toContain( - `unrelated = ${value}`, - ); - expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({ - status: "unchanged", - }); - }, - ); + it("replaces a registration containing a non-finite TOML float and preserves unrelated settings", async () => { + const value = "-inf"; + const root = await createTestTempDirectory("rea-client-toml-"); + const configPath = join(root, "config.toml"); + const original = `unrelated = ${value}\n[mcp_servers.rea]\ncommand = "old"\nstartup_timeout_sec = ${value}\n`; + await writeFile(configPath, original); + const client = { name: "codex", format: "toml", configPath } as const; + expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({ + status: "configured", + backupPath: `${configPath}.rea.backup`, + }); + expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original); + expect(await readFile(configPath, "utf8")).toContain( + `unrelated = ${value}`, + ); + expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({ + status: "unchanged", + }); + }); }); diff --git a/tests/boundary/filesystem/dependencyInstall.test.ts b/tests/boundary/filesystem/dependencyInstall.test.ts index f60dfc37f..fac9035c9 100644 --- a/tests/boundary/filesystem/dependencyInstall.test.ts +++ b/tests/boundary/filesystem/dependencyInstall.test.ts @@ -1,12 +1,20 @@ -import { copyFile, mkdir, writeFile } from "node:fs/promises"; -import { join } from "node:path"; -import { spawnSync } from "node:child_process"; +import { spawn, spawnSync } from "node:child_process"; +import { + copyFile, + mkdir, + readFile, + readdir, + writeFile, + symlink, +} from "node:fs/promises"; +import { dirname, join, relative } from "node:path"; import { describe, expect, it } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; const script = join(process.cwd(), "scripts", "check-dependency-install.mjs"); +const sourceRoot = process.cwd(); const fixture = async ( installedVersion?: string, @@ -88,4 +96,175 @@ describe("dependency install freshness", () => { expect(result.status).toBe(1); expect(result.stderr).toContain("alpha: installed 0.9.0, expected 1.0.0"); }); + + it("checks installed dependencies before restoring a cached build", async () => { + const root = await createBuildCacheFixture(); + const seeded = await runNpm(root, ["run", "build:cached"]); + expect(seeded.status, seeded.stderr).toBe(0); + + const rootLock = asRecord( + JSON.parse(await readFile(join(root, "package-lock.json"), "utf8")), + ); + const lockPackages = asRecord(rootLock?.packages); + const rootPackage = asRecord(lockPackages?.[""]); + const directDependencies = { + ...asRecord(rootPackage?.dependencies), + ...asRecord(rootPackage?.devDependencies), + }; + const dependencyName = Object.keys(directDependencies).sort()[0]; + if (dependencyName === undefined) + throw new Error("fixture has no direct dependencies"); + const dependencyPath = `node_modules/${dependencyName}`; + const expectedDependency = asRecord(lockPackages?.[dependencyPath]); + if (typeof expectedDependency?.version !== "string") + throw new Error( + `fixture is missing the locked ${dependencyName} version`, + ); + const installedLockPath = join(root, "node_modules", ".package-lock.json"); + const installedLock = asRecord( + JSON.parse(await readFile(installedLockPath, "utf8")), + ); + const packages = asRecord(installedLock?.packages); + const installedDependency = asRecord(packages?.[dependencyPath]); + if (installedDependency === undefined) + throw new Error(`fixture is missing installed ${dependencyName}`); + installedDependency.version = "0.0.0"; + await writeFile(installedLockPath, JSON.stringify(installedLock)); + + const blocked = await runNpm(root, ["run", "build:cached"]); + expect(blocked.status).toBe(1); + expect(blocked.stderr).toContain( + `${dependencyName}: installed 0.0.0, expected ${expectedDependency.version}`, + ); + + const cached = await runTurbo(root); + expect(cached.status, cached.stderr).toBe(0); + expect(`${cached.stdout}${cached.stderr}`).toMatch(/cache hit/u); + }, 60_000); }); + +const createBuildCacheFixture = async (): Promise => { + const root = await createTestTempDirectory("rea-build-cache-guard-"); + const packageJson = asRecord( + JSON.parse(await readFile(join(sourceRoot, "package.json"), "utf8")), + ); + const scripts = asRecord(packageJson?.scripts); + if ( + packageJson === undefined || + typeof scripts?.["build:cached"] !== "string" + ) + throw new Error("package is missing its cached build command"); + const paths = [ + ".nvmrc", + "package-lock.json", + "turbo.json", + "tsconfig.json", + "tsconfig.build.json", + "scripts/check-dependency-install.mjs", + "scripts/clean-build-output.mjs", + "scripts/generate-package-metadata.mjs", + "scripts/lib/generated-file.mjs", + "skills/reverse-engineer-anything/SKILL.md", + "src/generatedPackageMetadata.ts", + ]; + await Promise.all( + paths.map(async (path) => { + const destination = join(root, path); + await mkdir(dirname(destination), { recursive: true }); + await copyFile(join(sourceRoot, path), destination); + }), + ); + await writeFile( + join(root, "package.json"), + JSON.stringify({ + ...packageJson, + scripts: { + ...scripts, + build: "node scripts/cache-fixture-build.mjs", + }, + }), + ); + await writeFile( + join(root, "scripts/cache-fixture-build.mjs"), + 'import { mkdir, writeFile } from "node:fs/promises"; await mkdir("dist", { recursive: true }); await writeFile("dist/cache-fixture.js", "built");\n', + ); + await createNodeModulesLinks(root); + await writeFile( + join(root, "node_modules", ".package-lock.json"), + await readFile(join(sourceRoot, "node_modules", ".package-lock.json")), + ); + return root; +}; + +const createNodeModulesLinks = async (root: string): Promise => { + const source = join(sourceRoot, "node_modules"); + const destination = join(root, "node_modules"); + await mkdir(destination); + for (const entry of await readdir(source, { withFileTypes: true })) { + if (entry.name === ".package-lock.json") continue; + const sourcePath = join(source, entry.name); + const destinationPath = join(destination, entry.name); + if (entry.name.startsWith("@") && entry.isDirectory()) { + await mkdir(destinationPath); + for (const packageName of await readdir(sourcePath)) + await symlink( + join(sourcePath, packageName), + join(destinationPath, packageName), + process.platform === "win32" ? "junction" : undefined, + ); + } else { + await symlink( + sourcePath, + destinationPath, + process.platform === "win32" ? "junction" : undefined, + ); + } + } +}; + +const runNpm = (cwd: string, arguments_: readonly string[]) => + run("npm", arguments_, cwd); + +const runTurbo = (cwd: string) => + run( + process.execPath, + [ + join(cwd, "node_modules", "turbo", "bin", "turbo"), + "run", + "build", + "--output-logs=full", + ], + cwd, + ); + +const run = ( + command: string, + arguments_: readonly string[], + cwd: string, +): Promise<{ + readonly status: number; + readonly stdout: string; + readonly stderr: string; +}> => + new Promise((resolve, reject) => { + const child = spawn(command, [...arguments_], { + cwd, + env: process.env, + shell: process.platform === "win32", + stdio: ["ignore", "pipe", "pipe"], + }); + let stdout = ""; + let stderr = ""; + child.stdout.on("data", (chunk: Buffer) => (stdout += chunk.toString())); + child.stderr.on("data", (chunk: Buffer) => (stderr += chunk.toString())); + child.once("error", reject); + child.once("close", (status) => + resolve({ status: status ?? 1, stdout, stderr }), + ); + }); + +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null && !Array.isArray(value); + +const asRecord = (value: unknown): Record | undefined => + isRecord(value) ? value : undefined; diff --git a/tests/boundary/filesystem/directAnalysisPermissions.test.ts b/tests/boundary/filesystem/directAnalysisPermissions.test.ts index fcdef7e5d..cc2281938 100644 --- a/tests/boundary/filesystem/directAnalysisPermissions.test.ts +++ b/tests/boundary/filesystem/directAnalysisPermissions.test.ts @@ -54,7 +54,7 @@ describe("direct analysis snapshot files", () => { }); }); - it("replays a cache hit from the supplied path without root configuration", async () => { + it("does not replay unbound historical Evidence from a supplied snapshot", async () => { const directory = await createTestTempDirectory("rea-direct-snapshot-"); const snapshotPath = join(directory, "analysis.json"); const launcherPath = join(directory, "hopper-launcher"); @@ -97,6 +97,6 @@ describe("direct analysis snapshot files", () => { snapshotPath, }, ), - ).resolves.toEqual(evidence); + ).resolves.not.toEqual(evidence); }); }); diff --git a/tests/boundary/filesystem/dosComTarget.test.ts b/tests/boundary/filesystem/dosComTarget.test.ts index fb21eec72..ce5738b3d 100644 --- a/tests/boundary/filesystem/dosComTarget.test.ts +++ b/tests/boundary/filesystem/dosComTarget.test.ts @@ -6,36 +6,33 @@ import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; describe("DOS COM admission", () => { - it.each(["fixture.com", "fixture.hop", "fixture.js", "fixture.bin"])( - "requires explicit interpretation for %s", - async (name) => { - const directory = await createTestTempDirectory("rea-com-target-"); - const path = join(directory, name); - const bytes = Buffer.from("b83412c3", "hex"); - await writeFile(path, bytes); - const target = await parseBinaryTarget( + it("requires explicit interpretation even when a DOS COM uses a misleading suffix", async () => { + const directory = await createTestTempDirectory("rea-com-target-"); + const path = join(directory, "fixture.js"); + const bytes = Buffer.from("b83412c3", "hex"); + await writeFile(path, bytes); + const target = await parseBinaryTarget( + path, + directory, + "arm64", + undefined, + "dos-com", + ); + expect(target).toMatchObject({ + ok: true, + value: { path, - directory, - "arm64", - undefined, - "dos-com", - ); - expect(target).toMatchObject({ - ok: true, - value: { - path, - format: "dos-com", - kind: "executable", - architecture: "x86", - availableArchitectures: ["x86"], - sha256: createHash("sha256").update(bytes).digest("hex"), - }, - }); - expect(await readFile(path)).toEqual(bytes); - const detected = await parseBinaryTarget(path); - expect(detected.ok && detected.value.format).not.toBe("dos-com"); - }, - ); + format: "dos-com", + kind: "executable", + architecture: "x86", + availableArchitectures: ["x86"], + sha256: createHash("sha256").update(bytes).digest("hex"), + }, + }); + expect(await readFile(path)).toEqual(bytes); + const detected = await parseBinaryTarget(path); + expect(detected.ok && detected.value.format).not.toBe("dos-com"); + }); it.each([0, 0xff01])("rejects invalid file extent %i", async (length) => { const directory = await createTestTempDirectory("rea-com-target-"); const path = join(directory, "fixture.com"); diff --git a/tests/boundary/filesystem/installScript.test.ts b/tests/boundary/filesystem/installScript.test.ts index f6590e6cb..3abdf6a4f 100644 --- a/tests/boundary/filesystem/installScript.test.ts +++ b/tests/boundary/filesystem/installScript.test.ts @@ -30,18 +30,22 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => { expect(await readdir(fixture.temporary)).toEqual([]); }); - it.each(["22.19.0", "24.11.0", "26.0.0", "27.0.0"])( + it.each(["22.19.0", "24.11.0", "26.0.0"])( "accepts supported Node %s without installing or replacing it", async (version) => { const fixture = await createFixture(); - const result = await runInstaller(fixture, ["--version", "0.3.0"], { - FAKE_NODE_VERSION: version, - }); + const result = await runInstaller( + fixture, + ["--version", "0.3.0", "--dry-run"], + { + FAKE_NODE_VERSION: version, + }, + ); expect(result.stdout).toContain(`Runtime: Node.js ${version}`); }, ); - it.each(["22.18.9", "23.0.0", "24.10.9", "25.1.0", "26.0.0-rc.1"])( + it.each(["22.18.9", "24.10.9", "23.0.0", "26.0.0-rc.1"])( "rejects unsupported Node %s before invoking npm", async (version) => { const fixture = await createFixture(); @@ -77,15 +81,7 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => { }); }); - // Every installer failure must carry a recovery action. AGENTS.md requires - // actionable diagnostics, including for mismatch locations, so a failure that - // only states what went wrong is a regression. - // The installer is the one surface whose output is read directly by a person - // in a terminal, so the exact wording is a real contract and stays pinned. - // Alongside it, each row asserts the two properties that matter - // independently of phrasing: the failure is announced as a REA installation - // failure, and the message carries a recovery action. A copy edit then shows - // up as one reviewable diff instead of silently losing the guidance. + // Keep each distinct failure route while checking its reason and recovery. it.each([ [ "unsupported Node", @@ -158,11 +154,6 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => { expect(failure).toBeInstanceOf(Error); const stderr = String((failure as { stderr?: unknown }).stderr ?? ""); expect(stderr).toBe(message); - expect(stderr.startsWith("REA installation failed: ")).toBe(true); - // Actionable guidance must survive any rewording. - expect(stderr).toMatch( - /\b(?:Check|Repair|Reinstall|Install|Retry|then retry|use Node)\b/, - ); expect(await readdir(fixture.temporary)).toEqual([]); }, ); @@ -181,6 +172,95 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => { }); }); +describe("installer semantic version parsing", { timeout: 20_000 }, () => { + it.each(["1.2.3-01"])( + "rejects malformed semantic versions before invoking npm: %s", + async (version) => { + const fixture = await createFixture(); + await expect( + runInstaller(fixture, ["--version", version, "--dry-run"]), + ).rejects.toMatchObject({ + stderr: + "REA installation failed: version must be an exact semantic version.\n", + }); + await expect(readFile(fixture.npmLog, "utf8")).rejects.toMatchObject({ + code: "ENOENT", + }); + }, + ); + + it("rejects valid build metadata before printing a plan or invoking npm", async () => { + const fixture = await createFixture(); + const failure = await runInstaller(fixture, [ + "--version", + "1.2.3+build.01", + "--dry-run", + ]).then( + () => undefined, + (cause: unknown) => cause, + ); + expect(failure).toBeInstanceOf(Error); + const stderr = getProcessOutput(failure, "stderr"); + expect(stderr).toBe( + "REA installation failed: npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry.\n", + ); + expect(getProcessOutput(failure, "stdout")).not.toContain( + "REA install plan", + ); + await expect(readFile(fixture.npmLog, "utf8")).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + + it("accepts a valid prerelease version without build metadata", async () => { + const fixture = await createFixture(); + const result = await runInstaller(fixture, [ + "--version", + "1.2.3-alpha.0", + "--dry-run", + ]); + expect(result.stdout).toContain("Version: 1.2.3-alpha.0"); + await expect(readFile(fixture.npmLog, "utf8")).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + + it("rejects build metadata in the latest release tag before printing a plan", async () => { + const fixture = await createFixture(); + const failure = await runInstaller(fixture, [], { + FAKE_CURL_BODY: '{"tag_name":"rea-agents-1.2.3+build.01"}', + }).then( + () => undefined, + (cause: unknown) => cause, + ); + expect(failure).toBeInstanceOf(Error); + expect(getProcessOutput(failure, "stderr")).toBe( + "REA installation failed: npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry.\n", + ); + expect(getProcessOutput(failure, "stdout")).not.toContain( + "REA install plan", + ); + await expect(readFile(fixture.npmLog, "utf8")).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + + it("rejects an invalid semantic version from the latest release tag", async () => { + const fixture = await createFixture(); + await expect( + runInstaller(fixture, [], { + FAKE_CURL_BODY: '{"tag_name":"rea-agents-1.2.3-01"}', + }), + ).rejects.toMatchObject({ + stderr: + "REA installation failed: the latest release tag was invalid. Retry later or pass --version VERSION.\n", + }); + await expect(readFile(fixture.npmLog, "utf8")).rejects.toMatchObject({ + code: "ENOENT", + }); + }); +}); + interface InstallerFixture { readonly home: string; readonly bin: string; @@ -189,6 +269,20 @@ interface InstallerFixture { readonly reaLog: string; } +const getProcessOutput = ( + failure: unknown, + stream: "stdout" | "stderr", +): string => { + if (!(failure instanceof Error)) return ""; + if (stream === "stdout" && "stdout" in failure) { + return typeof failure.stdout === "string" ? failure.stdout : ""; + } + if (stream === "stderr" && "stderr" in failure) { + return typeof failure.stderr === "string" ? failure.stderr : ""; + } + return ""; +}; + const createFixture = async (): Promise => { const root = await createTestTempDirectory("rea-install-test-"); const home = join(root, "home"); @@ -197,19 +291,20 @@ const createFixture = async (): Promise => { const npmLog = join(root, "npm.log"); const reaLog = join(root, "rea.log"); await Promise.all([mkdir(home), mkdir(bin), mkdir(temporary)]); - await executable( - join(bin, "uname"), - '#!/bin/sh\n[ "$1" = "-m" ] && echo x86_64 || printf \'%s\\n\' "${FAKE_PLATFORM:-Linux}"\n', - ); - await executable( - join(bin, "node"), - `#!/bin/sh + await Promise.all([ + executable( + join(bin, "uname"), + '#!/bin/sh\n[ "$1" = "-m" ] && echo x86_64 || printf \'%s\\n\' "${FAKE_PLATFORM:-Linux}"\n', + ), + executable( + join(bin, "node"), + `#!/bin/sh if [ "$1" = "-p" ]; then printf '%s\n' "\${FAKE_NODE_VERSION:-24.18.0}"; else exec ${shellQuote(process.execPath)} "$@"; fi `, - ); - await executable( - join(bin, "npm"), - `#!/bin/sh + ), + executable( + join(bin, "npm"), + `#!/bin/sh printf '%s\n' "$*" >> "$FAKE_NPM_LOG" [ "$1" = "prefix" ] && { [ "\${FAKE_NPM_PREFIX_FAIL:-}" = "1" ] && exit 1; printf '%s\n' "$FAKE_NPM_PREFIX"; exit 0; } [ "\${FAKE_NPM_FAIL:-}" = "1" ] && exit 1 @@ -223,25 +318,27 @@ mkdir -p "$prefix/bin" cp "$FAKE_REA_SOURCE" "$prefix/bin/rea" chmod +x "$prefix/bin/rea" `, - ); - await executable( - join(bin, "curl"), - `#!/bin/sh + ), + executable( + join(bin, "curl"), + `#!/bin/sh [ "\${FAKE_CURL_FAIL:-}" = "1" ] && exit 1 if [ -n "\${FAKE_CURL_BODY:-}" ]; then printf '%s' "$FAKE_CURL_BODY"; else printf '%s' '{"tag_name":"rea-agents-0.3.0"}'; fi `, - ); - await executable( - join(bin, "rea-source"), - `#!/bin/sh + ), + executable( + join(bin, "rea-source"), + `#!/bin/sh printf '%s\n' "$*" >> "$FAKE_REA_LOG" [ "\${FAKE_REA_VERSION_FAIL:-}" = "1" ] && exit 1 [ "$1" = "--version" ] && printf '%s\n' "\${FAKE_REA_VERSION:-0.3.0}" exit 0 `, - ); - for (const command of ["chmod", "cp", "mkdir", "tr"]) - await symlink(`/usr/bin/${command}`, join(bin, command)); + ), + ...["chmod", "cp", "mkdir", "tr"].map((command) => + symlink(`/usr/bin/${command}`, join(bin, command)), + ), + ]); return { home, bin, temporary, npmLog, reaLog }; }; diff --git a/tests/boundary/filesystem/interfaceBuilderBundle.test.ts b/tests/boundary/filesystem/interfaceBuilderBundle.test.ts index a66984272..eebf6959d 100644 --- a/tests/boundary/filesystem/interfaceBuilderBundle.test.ts +++ b/tests/boundary/filesystem/interfaceBuilderBundle.test.ts @@ -13,6 +13,57 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; const compile = promisify(execFile); +type PlistValue = + | string + | number + | boolean + | Date + | Uint8Array + | PlistValue[] + | { [key: string]: PlistValue } + | null; + +const keyedArchiveHierarchy = ( + nodes: readonly { + readonly id: string; + readonly children: readonly number[]; + }[], +) => { + const nodeClass = 1 + nodes.length * 2; + const arrayClass = nodeClass + 1; + const objects: PlistValue[] = Array.from( + { length: arrayClass + 1 }, + () => null, + ); + objects[0] = "$null"; + for (const [index, node] of nodes.entries()) { + const nodeUid = 1 + index * 2; + objects[nodeUid] = { + $class: { UID: nodeClass }, + objectID: node.id, + children: { UID: nodeUid + 1 }, + }; + objects[nodeUid + 1] = { + $class: { UID: arrayClass }, + "NS.objects": node.children.map((child) => ({ UID: 1 + child * 2 })), + }; + } + objects[nodeClass] = { + $classname: "FixtureHierarchyNode", + $classes: ["FixtureHierarchyNode", "NSObject"], + }; + objects[arrayClass] = { + $classname: "__NSArrayI", + $classes: ["__NSArrayI", "NSArray", "NSObject"], + }; + return buildBinary({ + $archiver: "NSKeyedArchiver", + $version: 100000, + $objects: objects, + $top: { root: { UID: 1 } }, + }); +}; + describe("compiled Interface Builder bundle reader", () => { it("reads nib plist archives and reports provenance", async () => { const root = await createTestTempDirectory("rea-ib-test-"); @@ -149,7 +200,118 @@ describe("compiled Interface Builder bundle reader", () => { }, ]); }); +}); +describe("keyed archive hierarchy coverage", () => { + it("projects a deep keyed-archive hierarchy through NSArray wrappers", async () => { + const root = await createTestTempDirectory("rea-ib-test-"); + const bundle = join(root, "Example.app"); + const resources = join(bundle, "Contents", "Resources"); + await mkdir(resources, { recursive: true }); + const nodes = Array.from({ length: 40 }, (_, index) => ({ + id: `view-${index}`, + children: index === 39 ? [] : [index + 1], + })); + await writeFile(join(resources, "Deep.nib"), keyedArchiveHierarchy(nodes)); + + const analysis = await analyzeInterfaceBuilderBundle({ + bundlePath: bundle, + targetSha256: "f".repeat(64), + }); + + expect(analysis.documents[0]?.hierarchy_complete).toBe(true); + expect(analysis.graph.truncated).toBe(false); + expect(analysis.graph.coverage).toContainEqual( + expect.objectContaining({ + facet: "hierarchy:Contents/Resources/Deep.nib", + status: "complete", + omitted: 0, + }), + ); + expect( + analysis.graph.edges.filter( + ({ relation, id }) => + relation === "contains" && id.includes(":hierarchy:"), + ), + ).toHaveLength(40); + }); + + it("preserves source order for children in an archived collection", async () => { + const root = await createTestTempDirectory("rea-ib-test-"); + const bundle = join(root, "Example.app"); + const resources = join(bundle, "Contents", "Resources"); + await mkdir(resources, { recursive: true }); + await writeFile( + join(resources, "Branching.nib"), + keyedArchiveHierarchy([ + { id: "root", children: [1, 2] }, + { id: "first", children: [] }, + { id: "second", children: [] }, + ]), + ); + + const analysis = await analyzeInterfaceBuilderBundle({ + bundlePath: bundle, + targetSha256: "e".repeat(64), + }); + const archiveIdsByNodeId = new Map( + analysis.graph.nodes.map(({ id, attributes }) => [ + id, + attributes.interface_builder_object_id, + ]), + ); + const rootId = analysis.graph.nodes.find( + ({ attributes }) => attributes.interface_builder_object_id === "root", + )?.id; + const childNames = analysis.graph.edges + .filter( + ({ from, relation, id }) => + from === rootId && + relation === "contains" && + id.includes(":hierarchy:"), + ) + .map(({ to }) => (to === null ? null : archiveIdsByNodeId.get(to))); + expect(childNames).toEqual(["first", "second"]); + }); + + it.skipIf(process.platform !== "darwin")( + "projects a deep NSView archive produced by Foundation NSKeyedArchiver", + async () => { + const root = await createTestTempDirectory("rea-ib-test-"); + const bundle = join(root, "Example.app"); + const resources = join(bundle, "Contents", "Resources"); + await mkdir(resources, { recursive: true }); + const archive = join(resources, "Foundation.nib"); + await compile("/usr/bin/xcrun", [ + "swift", + "tests/conformance/native/keyed-archive-hierarchy.swift", + archive, + "40", + ]); + + const analysis = await analyzeInterfaceBuilderBundle({ + bundlePath: bundle, + targetSha256: "d".repeat(64), + }); + expect(analysis.documents[0]?.hierarchy_complete).toBe(true); + expect(analysis.graph.truncated).toBe(false); + expect( + analysis.graph.coverage.find( + ({ facet }) => + facet === "hierarchy:Contents/Resources/Foundation.nib", + ), + ).toMatchObject({ status: "complete", examined: 41, omitted: 0 }); + expect( + analysis.graph.edges.filter( + ({ relation, id }) => + relation === "contains" && id.includes(":hierarchy:"), + ), + ).toHaveLength(41); + }, + ); +}); + +describe("compiled Interface Builder bundle reader cancellation", () => { it("honors cancellation during directory traversal", async () => { const root = await createTestTempDirectory("rea-ib-test-"); const controller = new AbortController(); diff --git a/tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts b/tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts index 74fb9ab22..1871e59c5 100644 --- a/tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts +++ b/tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts @@ -1,10 +1,10 @@ -import { mkdir, rm, writeFile } from "node:fs/promises"; +import { lstat, mkdir, rm, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { createPackage } from "@electron/asar"; import { describe, expect, it } from "vitest"; -import { scanArtifactInventory } from "../../../src/application/ArtifactInventory.js"; +import { scanArtifactInventory } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { readJavaScriptArtifactFiles } from "../../../src/artifacts/javascript/JavaScriptArtifactFiles.js"; import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js"; import { DirectoryArtifactReader } from "../../../src/artifacts/DirectoryArtifactReader.js"; @@ -86,6 +86,24 @@ describe("JavaScript artifact ingestion content identities", () => { } }); + it("rejects a same-size ASAR replacement against the inventoried root digest", async () => { + const { source, archive } = await fixture(); + const snapshot = await scanArtifactInventory(archive); + const original = await lstat(archive); + await writeFile(join(source, "main.js"), "module.exports = 43;\n"); + await createPackage(source, archive); + expect((await lstat(archive)).size).toBe(original.size); + + const reader = new AsarArtifactReader(archive); + try { + await expect( + readJavaScriptArtifactFiles(reader, snapshot), + ).rejects.toMatchObject({ reason: "integrity" }); + } finally { + await reader.close(); + } + }); + it("preserves typed cancellation when the direct ASAR filesystem stream aborts", async () => { const { archive } = await fixture(); const snapshot = await scanArtifactInventory(archive); diff --git a/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts b/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts index cfab06c6b..3df383e7a 100644 --- a/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts +++ b/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts @@ -8,7 +8,7 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { readJavaScriptArtifactFiles } from "../../../src/artifacts/javascript/JavaScriptArtifactFiles.js"; import { reconstructJavaScriptArtifact } from "../../../src/application/javascript/JavaScriptArtifactReconstruction.js"; -import { scanArtifactInventory } from "../../../src/application/ArtifactInventory.js"; +import { scanArtifactInventory } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { type ArtifactEntry, type ArtifactReader, diff --git a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts index aab5fb66b..05e3f8e2d 100644 --- a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts +++ b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts @@ -88,6 +88,21 @@ describe("JavaScript export return-shape comparison", () => { expect(result.coverage.status).toBe("partial"); }); + it("orders multiple changed paths deterministically across Unicode names", async () => { + const graphs = await analyzeSources({ + left: `export default () => ({ type: "item", zeta: 1, alpha: 1, "éclair": 1 });`, + right: `export default () => ({ type: "item", zeta: 2, alpha: 2, "éclair": 2 });`, + }); + const first = compare(...graphs); + const second = compare(...graphs); + + expect(new Set(first.changes.map(({ path }) => path))).toEqual( + new Set(["/alpha", "/zeta", "/éclair"]), + ); + expect(first.changes).toEqual(second.changes); + expect(first.comparison_id).toBe(second.comparison_id); + }); + it("does not pair discriminants that an unknown trailing spread can overwrite", async () => { const [left, right] = await analyzeSources({ left: ` diff --git a/tests/boundary/filesystem/jsonUtf8Input.test.ts b/tests/boundary/filesystem/jsonUtf8Input.test.ts index c5274bd15..5d16c3c04 100644 --- a/tests/boundary/filesystem/jsonUtf8Input.test.ts +++ b/tests/boundary/filesystem/jsonUtf8Input.test.ts @@ -5,31 +5,28 @@ import { readJsonFile } from "../../../src/application/JsonFiles.js"; import { parseCliJsonInput } from "../../../src/cliJsonInput.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; describe("JSON file UTF-8 byte integrity", () => { - it.each([0x80, 0xe9, 0xff])( - "rejects invalid UTF-8 byte %s through both file input boundaries", - async (invalidByte) => { - const root = await createTestTempDirectory("rea-json-utf8-"); - const path = join(root, "input.json"); - await writeFile( - path, - Buffer.concat([ - Buffer.from('{"message":"'), - Buffer.from([invalidByte]), - Buffer.from('"}'), - ]), - ); - const fileInput = await readJsonFile(path); - const cliInput = await parseCliJsonInput(path, "compare_web_captures"); - expect(fileInput).toMatchObject({ - ok: false, - error: { reason: "invalid-json" }, - }); - expect(cliInput).toMatchObject({ - ok: false, - error: { input_reason: "invalid-json" }, - }); - }, - ); + it("rejects invalid UTF-8 through both file input boundaries", async () => { + const root = await createTestTempDirectory("rea-json-utf8-"); + const path = join(root, "input.json"); + await writeFile( + path, + Buffer.concat([ + Buffer.from('{"message":"'), + Buffer.from([0x80]), + Buffer.from('"}'), + ]), + ); + const fileInput = await readJsonFile(path); + const cliInput = await parseCliJsonInput(path, "compare_web_captures"); + expect(fileInput).toMatchObject({ + ok: false, + error: { reason: "invalid-json" }, + }); + expect(cliInput).toMatchObject({ + ok: false, + error: { input_reason: "invalid-json" }, + }); + }); it("preserves a legitimate UTF-8 replacement character", async () => { const root = await createTestTempDirectory("rea-json-utf8-control-"); const path = join(root, "input.json"); diff --git a/tests/boundary/filesystem/referenceSourceTestFilenames.test.ts b/tests/boundary/filesystem/referenceSourceTestFilenames.test.ts index a1cbaaf28..6a6ca8401 100644 --- a/tests/boundary/filesystem/referenceSourceTestFilenames.test.ts +++ b/tests/boundary/filesystem/referenceSourceTestFilenames.test.ts @@ -11,16 +11,12 @@ it("imports test-suffixed filenames with unchanged paths, hashes, and languages" const root = await createTestTempDirectory("rea-reference-test-filenames-"); const files = [ ["main_test.go", "package main\n", ["source", "test"], "Go"], - ["main_test.py", "assert True\n", ["source", "test"], "Python"], + ["test_helper.py", "assert True\n", ["source", "test"], "Python"], ["parser_spec.rs", "fn checks() {}\n", ["source", "test"], "Rust"], ["Widget_SPEC.TS", "export {};\n", ["source", "test"], "TypeScript"], ["main_test", "test fixture\n", ["test"], null], - ["main_spec", "spec fixture\n", ["test"], null], - ["test_helper.py", "assert True\n", ["source", "test"], "Python"], ["main.test.js", "export {};\n", ["source", "test"], "JavaScript"], - ["main.go", "package main\n", ["source"], "Go"], ["main_test_helper.py", "value = 1\n", ["source"], "Python"], - ["main_specimen.rs", "fn main() {}\n", ["source"], "Rust"], ["main_test.py.bak", "backup\n", ["unknown"], null], ] as const; await Promise.all( diff --git a/tests/boundary/filesystem/releaseWorkflow.test.ts b/tests/boundary/filesystem/releaseWorkflow.test.ts index e7a0c0935..dcacdde57 100644 --- a/tests/boundary/filesystem/releaseWorkflow.test.ts +++ b/tests/boundary/filesystem/releaseWorkflow.test.ts @@ -1,8 +1,420 @@ -import { readFile } from "node:fs/promises"; +import { execFile } from "node:child_process"; +import { readFile, writeFile } from "node:fs/promises"; +import { delimiter, join } from "node:path"; +import { promisify } from "node:util"; import { expect, it } from "vitest"; import { parse } from "yaml"; import { z } from "zod"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +const execFileAsync = promisify(execFile); +const stepSchema = z.object({ + name: z.string().optional(), + uses: z.string().optional(), + run: z.string().optional(), + if: z.string().optional(), + with: z.record(z.string(), z.unknown()).optional(), + env: z.record(z.string(), z.unknown()).optional(), +}); +const jobSchema = z.object({ + if: z.string().optional(), + outputs: z.record(z.string(), z.string()).optional(), + steps: z.array(stepSchema), +}); + +async function readReleaseWorkflow() { + return z + .object({ + on: z.record(z.string(), z.unknown()), + jobs: z.object({ + "release-please": jobSchema, + publish: jobSchema, + "publish-mcp": jobSchema, + }), + }) + .parse( + parse( + await readFile( + new URL("../../../.github/workflows/release.yml", import.meta.url), + "utf8", + ), + ), + ); +} + +it("requires explicit release preparation or publication instead of main pushes", async () => { + const workflow = await readReleaseWorkflow(); + expect(Object.keys(workflow.on)).toEqual(["workflow_dispatch"]); + expect(workflow.on.workflow_dispatch).toMatchObject({ + inputs: { + release_branch: { required: true, type: "string" }, + phase: { + required: true, + type: "choice", + default: "prepare", + options: ["prepare", "publish"], + }, + }, + }); + const release = workflow.jobs["release-please"].steps.find((step) => + step.uses?.startsWith("googleapis/release-please-action@"), + ); + expect(release?.with).toMatchObject({ + token: "${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}", + "target-branch": "${{ inputs.release_branch }}", + "skip-github-release": "${{ inputs.phase == 'prepare' }}", + "skip-github-pull-request": "${{ inputs.phase == 'publish' }}", + }); + const catalogCommit = workflow.jobs["release-please"].steps.find( + (step) => step.name === "Commit canonical release catalog", + ); + expect(catalogCommit?.env?.GH_TOKEN).toBe( + "${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}", + ); +}); + +it.skipIf(process.platform === "win32").each([ + { phase: "prepare", customToken: "" }, + { phase: "publish", customToken: "" }, + { phase: "prepare", customToken: "fixture-custom-token" }, + { phase: "publish", customToken: "fixture-custom-token" }, +])( + "accepts $phase with optional custom credentials: $customToken", + async ({ phase, customToken }) => { + const workflow = await readReleaseWorkflow(); + const steps = workflow.jobs["release-please"].steps; + const actionIndex = steps.findIndex((step) => + step.uses?.startsWith("googleapis/release-please-action@"), + ); + expect(actionIndex).toBeGreaterThanOrEqual(0); + const commands = steps + .slice(0, actionIndex) + .filter( + (step) => + step.if === undefined || + (phase === "publish" && step.if === "inputs.phase == 'publish'"), + ) + .map((step) => z.string().parse(step.run)); + const directory = await createTestTempDirectory("rea-release-credential-"); + const output = join(directory, "github-output"); + await writeFile( + join(directory, "gh"), + '#!/bin/sh\nprintf "%s\\n" "$GITHUB_SHA"\n', + { mode: 0o755 }, + ); + await expect( + execFileAsync( + "bash", + ["-e", "-o", "pipefail", "-c", commands.join("\n")], + { + env: { + ...process.env, + PATH: `${directory}${delimiter}${process.env.PATH ?? ""}`, + RELEASE_PLEASE_TOKEN: customToken, + GH_TOKEN: customToken || "fixture-built-in-token", + RELEASE_PHASE: phase, + RELEASE_BRANCH: "release/5.0.0", + GITHUB_REF: + phase === "prepare" + ? "refs/heads/main" + : "refs/heads/release/5.0.0", + GITHUB_REPOSITORY: "fixture/fixture", + GITHUB_SHA: "1111111111111111111111111111111111111111", + BRANCH_SHA: "1111111111111111111111111111111111111111", + GITHUB_OUTPUT: output, + }, + }, + ), + ).resolves.toMatchObject({ stderr: "" }); + expect(await readFile(output, "utf8")).toBe( + "sha=1111111111111111111111111111111111111111\n", + ); + }, +); + +it("binds npm and MCP publication to the same immutable release SHA", async () => { + const workflow = await readReleaseWorkflow(); + expect(workflow.jobs["release-please"].outputs).toEqual({ + release_created: "${{ steps.release.outputs.release_created }}", + sha: "${{ steps.release.outputs.sha }}", + }); + expect(workflow.jobs.publish.if).toBe( + "inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true'", + ); + expect(workflow.jobs["publish-mcp"].if).toBe( + "inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true' && needs.publish.result == 'success'", + ); + for (const job of [workflow.jobs.publish, workflow.jobs["publish-mcp"]]) { + const checkout = job.steps.find((step) => + step.uses?.startsWith("actions/checkout@"), + ); + expect(checkout?.with).toMatchObject({ + ref: "${{ needs.release-please.outputs.sha }}", + "persist-credentials": false, + }); + } + const preparation = [ + "Check out release pull request", + "Set up Node.js for generated documentation", + "Install dependencies", + "Regenerate release documentation", + "Commit canonical release catalog", + ]; + for (const name of preparation) { + expect( + workflow.jobs["release-please"].steps.find((step) => step.name === name) + ?.if, + ).toBe( + "inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'", + ); + } +}); + +it + .skipIf(process.platform === "win32") + .each(["main", "feature/new-work", "release/", "release/../main"])( + "rejects %s as a checkpoint before contacting GitHub", + async (releaseBranch) => { + const workflow = await readReleaseWorkflow(); + const command = z + .string() + .parse( + workflow.jobs["release-please"].steps.find( + (step) => step.name === "Validate release selection", + )?.run, + ); + await expect( + execFileAsync("bash", ["-e", "-o", "pipefail", "-c", command], { + env: { + ...process.env, + GITHUB_REF: "refs/heads/main", + RELEASE_BRANCH: releaseBranch, + RELEASE_PHASE: "prepare", + }, + }), + ).rejects.toMatchObject({ + code: 1, + stderr: expect.stringMatching(/release_branch/u), + }); + }, +); + +it.skipIf(process.platform === "win32").each([ + { phase: "prepare", ref: "refs/heads/feature/unreviewed-workflow" }, + { phase: "publish", ref: "refs/heads/main" }, + { phase: "publish", ref: "refs/heads/release/another-version" }, +])( + "rejects $phase from $ref before contacting GitHub", + async ({ phase, ref }) => { + const workflow = await readReleaseWorkflow(); + const command = z + .string() + .parse( + workflow.jobs["release-please"].steps.find( + (step) => step.name === "Validate release selection", + )?.run, + ); + await expect( + execFileAsync("bash", ["-e", "-o", "pipefail", "-c", command], { + env: { + ...process.env, + GITHUB_REF: ref, + RELEASE_BRANCH: "release/5.0.0", + RELEASE_PHASE: phase, + }, + }), + ).rejects.toMatchObject({ + code: 1, + stderr: expect.stringContaining(`Run ${phase} from`), + }); + }, +); + +it.skipIf(process.platform === "win32")( + "checks the checkpoint locally and resolves the branch tip separately", + async () => { + const workflow = await readReleaseWorkflow(); + const validate = z + .string() + .parse( + workflow.jobs["release-please"].steps.find( + (step) => step.name === "Validate release selection", + )?.run, + ); + expect(validate).not.toMatch(/\bgh\b/u); + const resolve = z + .string() + .parse( + workflow.jobs["release-please"].steps.find( + (step) => step.name === "Resolve release branch tip", + )?.run, + ); + expect(resolve).toContain("gh api"); + for (const releaseBranch of ["release/5.0.0", "release/5.0.0-rc.1"]) { + await expect( + execFileAsync("bash", ["-e", "-o", "pipefail", "-c", validate], { + env: { + ...process.env, + GITHUB_REF: "refs/heads/main", + RELEASE_BRANCH: releaseBranch, + RELEASE_PHASE: "prepare", + }, + }), + ).resolves.toMatchObject({ stderr: "" }); + } + }, +); + +it + .skipIf(process.platform === "win32") + .each(["", "release", "prepare ", "true"])( + "rejects phase %j before contacting GitHub", + async (phase) => { + const workflow = await readReleaseWorkflow(); + const command = z + .string() + .parse( + workflow.jobs["release-please"].steps.find( + (step) => step.name === "Validate release selection", + )?.run, + ); + await expect( + execFileAsync("bash", ["-e", "-o", "pipefail", "-c", command], { + env: { + ...process.env, + GITHUB_REF: "refs/heads/main", + RELEASE_BRANCH: "release/5.0.0", + RELEASE_PHASE: phase, + }, + }), + ).rejects.toMatchObject({ + code: 1, + stderr: expect.stringContaining("phase must be prepare or publish"), + }); + }, +); + +it.skipIf(process.platform === "win32").each([true, false])( + "creates a release only when the branch tip is the dispatched checkpoint (match: %s)", + async (match) => { + const workflow = await readReleaseWorkflow(); + const command = z + .string() + .parse( + workflow.jobs["release-please"].steps.find( + (step) => + step.name === + "Require the publish dispatch to match the branch tip", + )?.run, + ); + const result = execFileAsync( + "bash", + ["-e", "-o", "pipefail", "-c", command], + { + env: { + ...process.env, + GITHUB_SHA: "1111111111111111111111111111111111111111", + BRANCH_SHA: match + ? "1111111111111111111111111111111111111111" + : "2222222222222222222222222222222222222222", + }, + }, + ); + if (match) { + await expect(result).resolves.toMatchObject({ stderr: "" }); + } else { + await expect(result).rejects.toMatchObject({ + code: 1, + stderr: expect.stringContaining( + "refusing to create a release for a moved source", + ), + }); + } + }, +); + +it.skipIf(process.platform === "win32").each([true, false])( + "publishes only when the tag and provenance source match (match: %s)", + async (match) => { + const workflow = await readReleaseWorkflow(); + const command = z + .string() + .parse( + workflow.jobs["release-please"].steps.find( + (step) => + step.name === "Bind publication to the dispatched checkpoint", + )?.run, + ); + const result = execFileAsync( + "bash", + ["-e", "-o", "pipefail", "-c", command], + { + env: { + ...process.env, + GITHUB_SHA: "1111111111111111111111111111111111111111", + RELEASE_SHA: match + ? "1111111111111111111111111111111111111111" + : "2222222222222222222222222222222222222222", + }, + }, + ); + if (match) { + await expect(result).resolves.toMatchObject({ stderr: "" }); + } else { + await expect(result).rejects.toMatchObject({ + code: 1, + stderr: expect.stringContaining( + "refusing to publish mismatched source provenance", + ), + }); + } + }, +); + +it("runs release PR CI without enabling implementation pushes on release branches", async () => { + const workflow = z + .object({ + on: z.object({ + push: z.object({ branches: z.array(z.string()) }), + pull_request: z.object({ branches: z.array(z.string()) }), + }), + }) + .parse( + parse( + await readFile( + new URL("../../../.github/workflows/ci.yml", import.meta.url), + "utf8", + ), + ), + ); + expect(workflow.on.push.branches).toEqual(["main"]); + expect(workflow.on.pull_request.branches).toEqual( + expect.arrayContaining(["main", "release/*"]), + ); +}); + +it("checks website changes on release-branch pull requests", async () => { + const workflow = z + .object({ + on: z.object({ + pull_request: z.object({ branches: z.array(z.string()) }), + }), + }) + .parse( + parse( + await readFile( + new URL( + "../../../.github/workflows/website-check.yml", + import.meta.url, + ), + "utf8", + ), + ), + ); + expect(workflow.on.pull_request.branches).toEqual(["main", "release/*"]); +}); + // Publishing is irreversible. Keep the release authority invariant as a static // boundary check; it is not proof that package installation or publishing works. it("requires package verification before publishing and retains the registry canary", async () => { diff --git a/tests/boundary/filesystem/runtimeIdentification.test.ts b/tests/boundary/filesystem/runtimeIdentification.test.ts deleted file mode 100644 index 7ebc2e6b9..000000000 --- a/tests/boundary/filesystem/runtimeIdentification.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { writeFile } from "node:fs/promises"; -import { join } from "node:path"; - -import { - TextReader, - Uint8ArrayReader, - Uint8ArrayWriter, - ZipWriter, -} from "@zip.js/zip.js"; -import { describe, expect, it } from "vitest"; - -import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; - -import { runProviderAnalysis } from "../../../src/composition/directAnalysis.js"; -import { parseEvidence } from "../../../src/domain/evidence.js"; -import { - identifyRuntimes, - runtimeIdentificationResultSchema, -} from "../../../src/domain/runtimeIdentification.js"; - -describe("runtime identification", () => { - it("identifies APK runtime families and exposes missing semantic providers", async () => { - const root = await createTestTempDirectory("rea-runtime-"); - const path = join(root, "Fixture.apk"); - await writeApkRuntimeFixture(path); - - const inventory = parseEvidence( - await runProviderAnalysis(path, "inventory_artifact", {}), - ); - const first = identifyRuntimes({ inventory_evidence: [inventory] }); - const second = identifyRuntimes({ inventory_evidence: [inventory] }); - expect(first).toEqual(second); - expect(runtimeIdentificationResultSchema.parse(first)).toMatchObject({ - root_format: "apk", - coverage: { - status: "complete-within-inventory", - inventory_complete: true, - }, - }); - expect(first.runtimes.map(({ family }) => family)).toEqual([ - "android", - "javascript", - "jvm", - "native", - "webassembly", - ]); - expect(first.runtimes).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - family: "android", - inspection: "available", - provider_id: "rea-android-application", - }), - expect.objectContaining({ - family: "jvm", - inspection: "provider-missing", - provider_id: null, - }), - expect.objectContaining({ - family: "webassembly", - inspection: "provider-missing", - provider_id: null, - }), - expect.objectContaining({ - family: "javascript", - inspection: "available", - provider_id: "rea-javascript-application", - }), - expect.objectContaining({ - family: "native", - inspection: "provider-selection-required", - provider_id: null, - }), - ]), - ); - const widened = runtimeIdentificationResultSchema.parse({ - ...first, - root_format: "x".repeat(101), - source_evidence_ids: Array.from( - { length: 101 }, - () => inventory.evidence_id, - ), - limitations: Array.from({ length: 101 }, () => "x".repeat(4_097)), - runtimes: first.runtimes.map((runtime) => ({ - ...runtime, - ...(runtime.provider_id === null - ? { reason: "x".repeat(1_001) } - : { provider_id: `provider-${"x".repeat(200)}` }), - observations: runtime.observations.map((observation) => ({ - ...observation, - path: "x".repeat(4_097), - format: "x".repeat(101), - })), - })), - }); - expect(widened.limitations).toHaveLength(101); - expect(widened.runtimes[0]?.observations[0]?.path).toHaveLength(4_097); - const available = first.runtimes.find( - ({ inspection }) => inspection === "available", - ); - if (available === undefined) - throw new TypeError("Expected an available runtime provider"); - expect( - runtimeIdentificationResultSchema.safeParse({ - ...first, - runtimes: [{ ...available, provider_id: null, reason: "Missing." }], - }).success, - ).toBe(false); - }); - - it("accepts more than 100 supplied inventory Evidence records", async () => { - const root = await createTestTempDirectory("rea-runtime-pages-"); - const path = join(root, "Fixture.apk"); - const writer = new ZipWriter(new Uint8ArrayWriter()); - await writer.add("one.js", new TextReader("one")); - await writeFile(path, await writer.close()); - const inventory = parseEvidence( - await runProviderAnalysis(path, "inventory_artifact", {}), - ); - const result = identifyRuntimes({ - inventory_evidence: Array.from({ length: 101 }, () => inventory), - }); - - expect(result.source_evidence_ids).toHaveLength(101); - expect(result.coverage.status).toBe("complete-within-inventory"); - expect(result.runtimes).toContainEqual( - expect.objectContaining({ - family: "javascript", - observations: expect.arrayContaining([ - expect.objectContaining({ path: "one.js" }), - ]), - }), - ); - }); - - it("does not route nested DEX content to the APK-only provider", async () => { - const root = await createTestTempDirectory("rea-runtime-dex-"); - const path = join(root, "Fixture.zip"); - const writer = new ZipWriter(new Uint8ArrayWriter()); - await writer.add( - "classes.dex", - new Uint8ArrayReader( - Uint8Array.from([...Buffer.from("dex\n035\0"), 0, 0, 0, 0]), - ), - ); - await writeFile(path, await writer.close()); - - const inventory = parseEvidence( - await runProviderAnalysis(path, "inventory_artifact", {}), - ); - const result = identifyRuntimes({ inventory_evidence: [inventory] }); - - expect(result.runtimes).toContainEqual( - expect.objectContaining({ - family: "android", - inspection: "provider-selection-required", - provider_id: null, - }), - ); - }); -}); - -const writeApkRuntimeFixture = async (path: string): Promise => { - const writer = new ZipWriter(new Uint8ArrayWriter()); - await writer.add( - "AndroidManifest.xml", - new Uint8ArrayReader(Uint8Array.from([3, 0, 8, 0, 8, 0, 0, 0])), - ); - await writer.add( - "classes.dex", - new Uint8ArrayReader( - Uint8Array.from([...Buffer.from("dex\n035\0"), 0, 0, 0, 0]), - ), - ); - await writer.add( - "assets/Fixture.class", - new Uint8ArrayReader( - Uint8Array.from([0xca, 0xfe, 0xba, 0xbe, 0, 0, 0, 61]), - ), - ); - await writer.add( - "assets/module.wasm", - new Uint8ArrayReader(Uint8Array.from([0, 0x61, 0x73, 0x6d, 1, 0, 0, 0])), - ); - await writer.add("assets/main.js", new TextReader("bridge.call();")); - await writer.add( - "lib/arm64-v8a/libfixture.so", - new Uint8ArrayReader(Uint8Array.from([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1, 0])), - ); - await writeFile(path, await writer.close()); -}; diff --git a/tests/boundary/filesystem/setupConfig.test.ts b/tests/boundary/filesystem/setupConfig.test.ts index 2996f630c..e5e3ce25a 100644 --- a/tests/boundary/filesystem/setupConfig.test.ts +++ b/tests/boundary/filesystem/setupConfig.test.ts @@ -243,36 +243,34 @@ describe("JSON client configuration migration and provider paths", () => { expect(await readFile(configPath, "utf8")).toBe("not-json"); }); - it.each(["null", "[]", '"value"'])( - "refuses a non-object JSON root without overwriting %s", - async (original) => { - const directory = await createTestTempDirectory("rea-setup-"); - const configPath = join(directory, "mcp.json"); - await writeFile(configPath, original); - expect(await configureJsonClient({ name: "cursor", configPath })).toEqual( - { status: "failed", reason: "readback" }, - ); - expect(await readFile(configPath, "utf8")).toBe(original); - await expect( - readFile(`${configPath}.rea.backup`, "utf8"), - ).rejects.toThrow(); - }, - ); + it("refuses a non-object JSON root without overwriting it", async () => { + const original = "null"; + const directory = await createTestTempDirectory("rea-setup-"); + const configPath = join(directory, "mcp.json"); + await writeFile(configPath, original); + expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({ + status: "failed", + reason: "readback", + }); + expect(await readFile(configPath, "utf8")).toBe(original); + await expect( + readFile(`${configPath}.rea.backup`, "utf8"), + ).rejects.toThrow(); + }); - it.each(["null", "[]", '"value"'])( - "preserves a non-object mcpServers value %s", - async (servers) => { - const directory = await createTestTempDirectory("rea-setup-"); - const configPath = join(directory, "mcp.json"); - const original = `{"mcpServers":${servers}}`; - await writeFile(configPath, original); - expect(await configureJsonClient({ name: "cursor", configPath })).toEqual( - { status: "failed", reason: "readback" }, - ); - expect(await readFile(configPath, "utf8")).toBe(original); - await expect( - readFile(`${configPath}.rea.backup`, "utf8"), - ).rejects.toThrow(); - }, - ); + it("preserves a non-object mcpServers value", async () => { + const servers = "[]"; + const directory = await createTestTempDirectory("rea-setup-"); + const configPath = join(directory, "mcp.json"); + const original = `{"mcpServers":${servers}}`; + await writeFile(configPath, original); + expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({ + status: "failed", + reason: "readback", + }); + expect(await readFile(configPath, "utf8")).toBe(original); + await expect( + readFile(`${configPath}.rea.backup`, "utf8"), + ).rejects.toThrow(); + }); }); diff --git a/tests/boundary/filesystem/storeFileLock.test.ts b/tests/boundary/filesystem/storeFileLock.test.ts deleted file mode 100644 index e68f414dc..000000000 --- a/tests/boundary/filesystem/storeFileLock.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; -import { lstat, readdir, rm } from "node:fs/promises"; -import { join } from "node:path"; - -import { describe, expect, it } from "vitest"; - -import { - createStoreFileLock, - removeStaleStoreFileLock, -} from "../../../src/application/StoreFileLock.js"; -import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; - -describe("store lock cleanup ownership", () => { - it.each(["released", "replaced"] as const)( - "preserves a successor lock when an old %s owner cleans up", - async (state) => { - const root = await createTestTempDirectory("rea-store-lock-owner-"); - const path = join(root, "transaction.lock"); - const previous = await createStoreFileLock(path); - if (state === "released") await previous.release(); - else await rm(path, { recursive: true }); - const successor = await createStoreFileLock(path); - try { - await previous.release(); - await expect(lstat(path)).resolves.toBeDefined(); - await expect(createStoreFileLock(path)).rejects.toMatchObject({ - code: "EEXIST", - }); - } finally { - await successor.release(); - } - }, - ); -}); - -describe("store lock acquisition and recovery", () => { - it("admits one concurrent owner and removes all preparation directories", async () => { - const root = await createTestTempDirectory("rea-store-lock-contenders-"); - const path = join(root, "transaction.lock"); - const results = await Promise.allSettled( - Array.from({ length: 8 }, () => createStoreFileLock(path)), - ); - try { - expect( - results.filter((result) => result.status === "fulfilled"), - ).toHaveLength(1); - for (const result of results) - if (result.status === "rejected") - expect(result.reason).toMatchObject({ code: "EEXIST" }); - expect(await readdir(root)).toEqual(["transaction.lock"]); - expect(await removeStaleStoreFileLock(path)).toBe(false); - } finally { - await Promise.all( - results.map((result) => - result.status === "fulfilled" ? result.value.release() : undefined, - ), - ); - } - expect(await readdir(root)).toEqual([]); - }); - - it("recovers a directory lock abandoned by an exited process", async () => { - const root = await createTestTempDirectory("rea-store-lock-exited-"); - const path = join(root, "transaction.lock"); - await promisify(execFile)( - process.execPath, - ["tests/fixtures/abandonStoreFileLock.mjs", path], - { timeout: 5_000 }, - ); - expect((await lstat(path)).isDirectory()).toBe(true); - expect(await removeStaleStoreFileLock(path)).toBe(true); - const successor = await createStoreFileLock(path); - try { - expect(await removeStaleStoreFileLock(path)).toBe(false); - } finally { - await successor.release(); - } - expect(await readdir(root)).toEqual([]); - }); -}); diff --git a/tests/boundary/filesystem/temporaryDirectory.test.ts b/tests/boundary/filesystem/temporaryDirectory.test.ts deleted file mode 100644 index 14fa55ab2..000000000 --- a/tests/boundary/filesystem/temporaryDirectory.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { access, realpath, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { dirname, join } from "node:path"; - -import { describe, expect, it } from "vitest"; - -import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; - -describe("test temporary directory ownership", { concurrent: false }, () => { - let completedDirectory: string | undefined; - - it("creates a canonical directory owned by the current test", async () => { - const directory = await createTestTempDirectory("rea-owned-temp-"); - completedDirectory = directory; - - expect(directory).toBe(await realpath(directory)); - expect(dirname(directory)).toBe(await realpath(tmpdir())); - await writeFile(join(directory, "nested.txt"), "owned"); - }); - - it("removes the prior test directory after that test finishes", async () => { - const directory = completedDirectory; - if (directory === undefined) - throw new Error("Prior test did not create a directory"); - await expect(access(directory)).rejects.toMatchObject({ - code: "ENOENT", - }); - }); - - it.each(["", "../escape-", "/absolute-", "missing-dash"])( - "rejects unsafe prefix %j", - async (prefix) => { - await expect(createTestTempDirectory(prefix)).rejects.toThrow(TypeError); - }, - ); -}); diff --git a/tests/boundary/filesystem/zipDetection.test.ts b/tests/boundary/filesystem/zipDetection.test.ts index 236589f14..8ab81112a 100644 --- a/tests/boundary/filesystem/zipDetection.test.ts +++ b/tests/boundary/filesystem/zipDetection.test.ts @@ -3,27 +3,22 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { classifyRoot } from "../../../src/application/ArtifactInventory/classify.js"; +import { classifyRoot } from "../../../src/artifacts/inventory/classify.js"; import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; describe("ZIP signature detection", () => { it.each([ - [0x03, 0x04, true], - [0x05, 0x06, true], - [0x07, 0x08, true], - [0x03, 0x06, false], - [0x03, 0x08, false], - [0x05, 0x04, false], - [0x05, 0x08, false], - [0x07, 0x04, false], - [0x07, 0x06, false], + { signature: [0x50, 0x4b, 0x05, 0x06], archive: true }, + { signature: [0x50, 0x4b, 0x03, 0x06], archive: false }, + { signature: [0x50, 0x4b, 0x03, 0x04], archive: true }, + { signature: [0x50, 0x4b, 0x07, 0x08], archive: true }, ] as const)( - "classifies PK %i %i with archive signature %s", - async (third, fourth, archive) => { + "routes representative ZIP signature $signature through inventory and target parsing", + async ({ signature, archive }) => { const root = await createTestTempDirectory("rea-zip-signature-"); const path = join(root, "input"); - await writeFile(path, Buffer.from([0x50, 0x4b, third, fourth])); + await writeFile(path, Buffer.from(signature)); expect(await classifyRoot(path, false)).toBe(archive ? "zip" : "file"); const target = await parseBinaryTarget(path); if (archive) diff --git a/tests/boundary/mcp/androidAnalysisMcp.test.ts b/tests/boundary/mcp/androidAnalysisMcp.test.ts index 55e335335..dcb8297ed 100644 --- a/tests/boundary/mcp/androidAnalysisMcp.test.ts +++ b/tests/boundary/mcp/androidAnalysisMcp.test.ts @@ -1,5 +1,7 @@ import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { expect, it as test, onTestFinished } from "vitest"; +import { z } from "zod"; import { access } from "node:fs/promises"; import { ANDROID_TOOL_CONTRACTS } from "../../../src/contracts/android/androidToolContracts.js"; import { parseEvidence } from "../../../src/domain/evidence.js"; @@ -92,8 +94,15 @@ it("publishes and executes all APK contracts with inline Evidence and no active await server.connect(serverTransport); await client.connect(clientTransport); const advertised = await client.listTools(); + const wireTools = new Map(advertised.tools.map((tool) => [tool.name, tool])); for (const contract of ANDROID_TOOL_CONTRACTS) { - const wire = advertised.tools.find((tool) => tool.name === contract.name); + const wire = wireTools.get(contract.name); + if (wire === undefined || wire.outputSchema === undefined) + throw new Error(`Missing advertised output schema for ${contract.name}`); + const validateOutput = new Ajv2020({ + strict: false, + validateFormats: false, + }).compile(z.record(z.string(), z.unknown()).parse(wire.outputSchema)); expect(wire?.annotations).toMatchObject(contract.annotations); expect(contract.effects).toMatchObject({ mutatesTarget: false, @@ -120,6 +129,7 @@ it("publishes and executes all APK contracts with inline Evidence and no active arguments: input, }); expect(response.isError, JSON.stringify(response)).not.toBe(true); + expect(validateOutput(response.structuredContent)).toBe(true); const result = contract.outputSchema.parse(response.structuredContent); const evidence = parseEvidence(result.evidence); expect(result.result).toEqual(evidence.normalized_result); diff --git a/tests/boundary/mcp/artifactEntryOrderMcp.test.ts b/tests/boundary/mcp/artifactEntryOrderMcp.test.ts deleted file mode 100644 index a3ad28671..000000000 --- a/tests/boundary/mcp/artifactEntryOrderMcp.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { join } from "node:path"; - -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; -import { expect, it } from "vitest"; -import { z } from "zod"; - -import { parseConfig } from "../../../src/config.js"; -import { createBinarySession } from "../../../src/composition/binary.js"; -import { createServer } from "../../../src/server/createServer.js"; -import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js"; -import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js"; -import { - artifactParentPaths, - writeOrderedZip, -} from "../../fixtures/artifactEntryOrder.js"; -import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; - -it("returns correct child-first ZIP containment through MCP inspection", async () => { - const root = await createTestTempDirectory("rea-entry-order-mcp-"); - const archive = join(root, "child-first.zip"); - await writeOrderedZip(archive, ["pkg/sub/data.txt", "pkg/sub/", "pkg/"]); - const configured = parseConfig({}); - if (!configured.ok) throw configured.error; - const session = createBinarySession(configured.value); - const server = createServer(session, session); - const client = new Client({ name: "entry-order-test", version: "1" }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - try { - await server.connect(serverTransport); - await client.connect(clientTransport); - expect( - ( - await client.callTool({ - name: "open_binary", - arguments: { path: archive }, - }) - ).isError, - ).not.toBe(true); - const called = await client.callTool({ - name: "inspect_artifact", - arguments: {}, - }); - expect(called.isError, JSON.stringify(called.structuredContent)).not.toBe( - true, - ); - const inspection = artifactInspectionResultSchema.parse( - z.object({ result: z.unknown() }).parse(called.structuredContent).result, - ); - const substep = inspection.substeps[0]; - if (substep === undefined) throw new Error("Missing inventory Evidence"); - const inventory = artifactInventoryResultSchema.parse( - substep.evidence.normalized_result, - ); - expect(artifactParentPaths(inventory)).toEqual({ - ".": null, - pkg: ".", - "pkg/sub": "pkg", - "pkg/sub/data.txt": "pkg/sub", - }); - expect(inspection.coverage.status).toBe("complete-within-substeps"); - } finally { - await client.close(); - await server.close(); - await session.close(); - } -}); diff --git a/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts b/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts index a0bf85875..5afbb74ad 100644 --- a/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts +++ b/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts @@ -5,8 +5,6 @@ import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js"; import { expect, it } from "vitest"; import { z } from "zod"; -import { parseConfig } from "../../../src/config.js"; -import { createBinarySession } from "../../../src/composition/binary.js"; import { keyedArchiveResultSchema } from "../../../src/domain/apple/keyedArchive.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; @@ -71,25 +69,19 @@ it("inspects a standalone keyed archive through MCP with original object identit } }); -it("returns every artifact occurrence in one MCP call", async () => { +it("returns all 520 ZIP file occurrences in one inspect_artifact MCP result", async () => { const directory = await createTestTempDirectory("rea-artifact-inline-"); const archive = join(directory, "many.zip"); const writer = new ZipWriter(new Uint8ArrayWriter()); const fileCount = 520; - for (let index = 0; index < fileCount; index += 1) { + for (let index = 0; index < fileCount; index += 1) await writer.add( `files/${String(index)}.txt`, new TextReader(`file-${String(index)}`), ); - } await writeFile(archive, await writer.close()); - const configured = parseConfig({ - HOPPER_LAUNCHER_PATH: "/rea-unconfigured-deep-provider/hopper", - }); - expect(configured.ok).toBe(true); - if (!configured.ok) throw configured.error; - const session = createBinarySession(configured.value); + const session = createTestBinarySession(new ArtifactProvider()); const server = createServer(session, session); const client = new Client({ name: "artifact-inline-test", version: "1" }); const [clientTransport, serverTransport] = @@ -102,43 +94,6 @@ it("returns every artifact occurrence in one MCP call", async () => { arguments: { path: archive }, }); expect(opened.isError).not.toBe(true); - const names = (await client.listTools()).tools.map(({ name }) => name); - expect(names).toContain("inspect_artifact"); - expect(names).toContain("inspect_asset_catalog"); - expect(names).not.toContain("inventory_artifact"); - const status = await client.callTool({ - name: "binary_session", - arguments: {}, - }); - expect(status.isError).not.toBe(true); - const capabilities = z - .object({ - result: z.object({ - capabilities: z.array( - z.object({ - operation: z.string(), - available: z.boolean(), - reason: z.string().nullable(), - }), - ), - }), - }) - .parse(status.structuredContent).result.capabilities; - expect(capabilities.map(({ operation }) => operation)).not.toContain( - "inventory_artifact", - ); - expect( - capabilities.find( - ({ operation }) => operation === "inspect_asset_catalog", - ), - ).toMatchObject( - process.platform === "darwin" - ? { available: true, reason: null } - : { - available: false, - reason: "Apple asset catalogs require macOS assetutil.", - }, - ); const result = await client.callTool({ name: "inspect_artifact", arguments: {}, @@ -157,23 +112,14 @@ it("returns every artifact occurrence in one MCP call", async () => { }), }), ), - observations: z.array(z.unknown()), - derived_relationships: z.array(z.unknown()), - hypotheses: z.array(z.unknown()), - unexplored_branches: z.array(z.unknown()), - next_probes: z.array(z.unknown()), }) .parse( z.object({ result: z.unknown() }).parse(result.structuredContent) .result, ); - const occurrences = - inspection.substeps[0]?.evidence.normalized_result.occurrences; - if (occurrences === undefined) - throw new Error("inspection omitted the artifact inventory"); - expect(occurrences).toHaveLength(fileCount + 1); - expect(inspection.observations.length).toBeGreaterThan(0); - expect(inspection.derived_relationships.length).toBeGreaterThan(0); + expect( + inspection.substeps[0]?.evidence.normalized_result.occurrences, + ).toHaveLength(fileCount + 1); } finally { await Promise.allSettled([client.close(), server.close(), session.close()]); } diff --git a/tests/boundary/mcp/browserMcp.test.ts b/tests/boundary/mcp/browserMcp.test.ts index 21e615e62..a61576ee7 100644 --- a/tests/boundary/mcp/browserMcp.test.ts +++ b/tests/boundary/mcp/browserMcp.test.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; +import { webPageInspectionSchema } from "../../../src/domain/browserObservation.js"; import { JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE } from "../../../src/contracts/javascript/javascriptRuntimeReconciliationExample.js"; import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js"; import { createServer } from "../../../src/server/createServer.js"; @@ -190,6 +191,40 @@ const verifySessionAndComparisonTools = async ( expect(compared.structuredContent).toMatchObject({ result: { overall_status: "unknown" }, }); + const parsedCapture = webPageInspectionSchema.parse(capture); + const socket = parsedCapture.network.websocket_events.find( + (event) => event.payload_shape?.json_shape != null, + ); + const payload = socket?.payload_shape; + if (socket === undefined || payload == null || payload.json_shape === null) + throw new Error("Missing deep WebSocket JSON shape fixture"); + const malformedCapture = { + ...parsedCapture, + network: { + ...parsedCapture.network, + websocket_events: [ + { + ...socket, + payload_shape: { + ...payload, + json_shape: { + ...payload.json_shape, + properties: [{ path: "/event", types: [42], observations: 1 }], + }, + }, + }, + ], + }, + }; + const malformedComparison = await connected.client.callTool({ + name: "compare_web_captures", + arguments: { + before: { inspection: malformedCapture }, + after: { inspection: capture }, + }, + }); + expect(malformedComparison.isError).toBe(true); + expect(JSON.stringify(malformedComparison.content)).toContain("types"); const incompleteComparison = await connected.client.callTool({ name: "compare_web_captures", arguments: { before: { inspection: capture } }, diff --git a/tests/boundary/mcp/createServer.test.ts b/tests/boundary/mcp/createServer.test.ts deleted file mode 100644 index 781a4162a..000000000 --- a/tests/boundary/mcp/createServer.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect } from "vitest"; - -import { observed as ok } from "../../fixtures/analysisExecution.js"; -import { mcpTest } from "../../support/mcp/mcpFixture.js"; -import { createServer } from "../../../src/server/createServer.js"; - -const analysis = { - execute: () => Promise.resolve(ok(null)), -}; - -describe("MCP server composition", () => { - mcpTest("does not advertise unavailable session tools", async ({ mcp }) => { - const server = createServer(analysis); - const client = await mcp.connect(server, { - name: "composition-test", - version: "1.0.0", - }); - expect(client.getInstructions()).not.toContain("open_binary"); - const names = (await client.listTools()).tools.map(({ name }) => name); - expect(names).not.toContain("compare_managed_members"); - }); -}); diff --git a/tests/boundary/mcp/electronMcp.test.ts b/tests/boundary/mcp/electronMcp.test.ts index 72d8892ed..4cc589e70 100644 --- a/tests/boundary/mcp/electronMcp.test.ts +++ b/tests/boundary/mcp/electronMcp.test.ts @@ -10,6 +10,7 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; import type { ElectronActiveObservationPort } from "../../../src/application/javascript/ElectronActiveObservationPort.js"; +import { analyzeJavaScriptApplication } from "../../../src/application/javascript/JavaScriptApplicationService.js"; import { CdpElectronProvider } from "../../../src/browser/CdpElectronProvider.js"; import { createServer } from "../../../src/server/createServer.js"; import { observed } from "../../fixtures/analysisExecution.js"; @@ -270,12 +271,18 @@ it("exposes the target-free static JavaScript application workflow", async () => await server.connect(serverTransport); await client.connect(clientTransport); - const analyzed = await client.callTool({ - name: "analyze_javascript_application", - arguments: { input_path: root }, - }); + const progress: number[] = []; + const analyzed = await client.callTool( + { + name: "analyze_javascript_application", + arguments: { input_path: root }, + }, + { onprogress: (update) => progress.push(update.progress) }, + ); expect(analyzed.isError).not.toBe(true); + expect(progress[0]).toBe(0); + expect(progress.at(-1)).toBe(1); const projected = z .object({ evidence_id: z.string(), @@ -314,6 +321,9 @@ it("exposes the target-free static JavaScript application workflow", async () => expect(projected.result.graph.nodes.length).toBeGreaterThan(0); expect(projected.result.semantic_graph.nodes.length).toBeGreaterThan(0); expect(projected.evidence.operation).toBe("analyze_javascript_application"); + const direct = await analyzeJavaScriptApplication({ input_path: root }); + if (!direct.ok) throw direct.error; + expect(projected.evidence_id).toBe(direct.value.evidence_id); }); const evidenceFor = (value: unknown) => { diff --git a/tests/boundary/mcp/enhancedTools.06-rejects-incomplete-function-dossiers-at.test.ts b/tests/boundary/mcp/enhancedTools.06-rejects-incomplete-function-dossiers-at.test.ts index 0b9fe43ef..d0ac870fc 100644 --- a/tests/boundary/mcp/enhancedTools.06-rejects-incomplete-function-dossiers-at.test.ts +++ b/tests/boundary/mcp/enhancedTools.06-rejects-incomplete-function-dossiers-at.test.ts @@ -1,123 +1,18 @@ -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { afterEach, describe, expect, it } from "vitest"; -import { PROCEDURES, inventory } from "./enhancedToolsHarness.js"; -import type { AnalysisOperationPort } from "../../../src/application/AnalysisProvider.js"; -import { createServer } from "../../../src/server/createServer.js"; -import { observed as ok } from "../../fixtures/analysisExecution.js"; +import { functionDossierSchema } from "../../../src/domain/hopperValues.js"; +import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js"; +import { observed } from "../../fixtures/analysisExecution.js"; +import { closeEnhancedToolResources, connect } from "./enhancedToolsHarness.js"; -const fixturePort = (): AnalysisOperationPort => ({ - execute: (name, arguments_) => { - switch (name) { - case "list_procedures": - return Promise.resolve(ok(inventory(PROCEDURES))); - case "list_names": - return Promise.resolve( - ok( - inventory({ - "0x10": "_OBJC_CLASS_$_Fixture", - "0x11": "_OBJC_CLASS_$_Fixture", - "0x12": "_OBJC_PROTOCOL_$_FixtureDelegate", - "0x13": "entry", - }), - ), - ); - case "procedure_pseudo_code": { - const procedure = arguments_.procedure; - return Promise.resolve( - ok(typeof procedure === "string" ? `pseudo:${procedure}` : "invalid"), - ); - } - case "procedure_callees": { - const procedure = arguments_.procedure; - return Promise.resolve( - ok( - procedure === "0x1" - ? ["0x2", "0x3"] - : procedure === "0x2" - ? ["0x1"] - : [], - ), - ); - } - case "procedure_callers": - return Promise.resolve(ok(["0x9"])); - case "address_name": - return Promise.resolve(ok(arguments_.address ?? null)); - case "xrefs": - return Promise.resolve(ok(["0x20", "0x21"])); - case "resolve_containing_procedure": - return Promise.resolve( - ok({ - query_address: - typeof arguments_.address === "string" - ? arguments_.address - : "0x0", - found: false, - procedure: null, - reason: "not_in_procedure", - }), - ); - case "list_segments": - return Promise.resolve( - ok([{ name: "__TEXT", start: "0x1000", end: "0x2000" }]), - ); - case "list_documents": - return Promise.resolve(ok(["fixture"])); - case "list_strings": - return Promise.resolve(ok(inventory({ "0x30": "hello" }))); - case "analyze_function": - return Promise.resolve( - ok({ - procedure: { - address: "0x1", - name: "entry", - signature: null, - locals: [], - }, - pseudocode: "return 0;", - assembly: [], - comments: [], - callers: [], - callees: [], - incoming_references: [], - outgoing_references: [], - referenced_strings: [], - referenced_names: [], - basic_blocks: [], - }), - ); - default: - return Promise.resolve(ok(null)); - } - }, -}); +afterEach(closeEnhancedToolResources); -const resources: Array<{ close(): Promise }> = []; - -afterEach(async () => { - await Promise.all( - resources.splice(0).map(async (resource) => resource.close()), - ); -}); - -const connect = async (analysis: AnalysisOperationPort = fixturePort()) => { - const server = createServer(analysis); - const client = new Client({ name: "enhanced-test", version: "1.0.0" }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - resources.push(client, server); - await server.connect(serverTransport); - await client.connect(clientTransport); - return client; -}; - -describe("enhanced MCP tools", () => { - it("rejects incomplete function dossiers at the application boundary", async () => { +describe("analyze_function MCP producer validation", () => { + it("rejects an incomplete function dossier", async () => { const client = await connect({ execute: () => Promise.resolve( - ok({ + observed({ procedure: { address: "0x1", name: "entry" }, pseudocode: { text: "plausible but incomplete" }, }), @@ -127,46 +22,40 @@ describe("enhanced MCP tools", () => { name: "analyze_function", arguments: { procedure: "0x1" }, }); + expect(result.isError).toBe(true); - const text = result.content.find((item) => item.type === "text"); - expect(text?.type === "text" ? text.text : "").toBe( - JSON.stringify(result.structuredContent), - ); + expect(result.content).toContainEqual({ + type: "text", + text: JSON.stringify(result.structuredContent), + }); }); - it("rejects deceptive function dossier collection metadata", async () => { - const malformedPort = fixturePort(); + it("rejects a malformed collection in an otherwise complete dossier", async () => { + const dossier = functionDossierSchema.parse(ghidraFunctionDossier()); const client = await connect({ - execute: async (name, arguments_, options) => { - const result = await malformedPort.execute(name, arguments_, options); - if (!result.ok || name !== "analyze_function") return result; - const dossier = result.value.result; - if ( - typeof dossier !== "object" || - dossier === null || - Array.isArray(dossier) - ) - return result; - return ok({ - ...dossier, - comments: { - items: [], - total: 0, - returned: 1, - truncated: false, - next_offset: null, - }, - }); - }, + execute: () => + Promise.resolve( + observed({ + ...dossier, + comments: { + items: [], + total: 0, + returned: 1, + truncated: false, + next_offset: null, + }, + }), + ), }); const result = await client.callTool({ name: "analyze_function", arguments: { procedure: "0x1" }, }); + expect(result.isError).toBe(true); - const text = result.content.find((item) => item.type === "text"); - expect(text?.type === "text" ? text.text : "").toBe( - JSON.stringify(result.structuredContent), - ); + expect(result.content).toContainEqual({ + type: "text", + text: JSON.stringify(result.structuredContent), + }); }); }); diff --git a/tests/boundary/mcp/enhancedTools.callPathsAndBatches.test.ts b/tests/boundary/mcp/enhancedTools.callPathsAndBatches.test.ts index 8e15ce3d1..4ae1125d2 100644 --- a/tests/boundary/mcp/enhancedTools.callPathsAndBatches.test.ts +++ b/tests/boundary/mcp/enhancedTools.callPathsAndBatches.test.ts @@ -12,7 +12,18 @@ afterEach(closeEnhancedToolResources); describe("enhanced MCP tools", () => { it("returns Evidence IDs and a complete call path", async () => { - const client = await connect(); + const client = await connect({ + execute: async (_name, arguments_) => { + const procedure = arguments_.procedure; + return ok( + procedure === "0x1" + ? ["0x2", "0x3"] + : procedure === "0x2" + ? ["0x1"] + : [], + ); + }, + }); const result = await client.callTool({ name: "trace_call_path", arguments: { diff --git a/tests/boundary/mcp/enhancedTools.inputValidation.test.ts b/tests/boundary/mcp/enhancedTools.inputValidation.test.ts index 9f6af2db2..f6c881c37 100644 --- a/tests/boundary/mcp/enhancedTools.inputValidation.test.ts +++ b/tests/boundary/mcp/enhancedTools.inputValidation.test.ts @@ -1,136 +1,13 @@ -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { afterEach, describe, expect, it } from "vitest"; -import { PROCEDURES, emptyBounded, inventory } from "./enhancedToolsHarness.js"; -import type { AnalysisOperationPort } from "../../../src/application/AnalysisProvider.js"; -import { createServer } from "../../../src/server/createServer.js"; +import { closeEnhancedToolResources, connect } from "./enhancedToolsHarness.js"; import { observed as ok } from "../../fixtures/analysisExecution.js"; -const fixturePort = (): AnalysisOperationPort => ({ - execute: (name, arguments_) => { - switch (name) { - case "list_procedures": - return Promise.resolve(ok(inventory(PROCEDURES))); - case "list_names": - return Promise.resolve( - ok( - inventory({ - "0x10": "_OBJC_CLASS_$_Fixture", - "0x11": "_OBJC_CLASS_$_Fixture", - "0x12": "_OBJC_PROTOCOL_$_FixtureDelegate", - "0x13": "entry", - }), - ), - ); - case "procedure_pseudo_code": { - const procedure = arguments_.procedure; - return Promise.resolve( - ok(typeof procedure === "string" ? `pseudo:${procedure}` : "invalid"), - ); - } - case "procedure_callees": { - const procedure = arguments_.procedure; - return Promise.resolve( - ok( - procedure === "0x1" - ? ["0x2", "0x3"] - : procedure === "0x2" - ? ["0x1"] - : [], - ), - ); - } - case "procedure_callers": - return Promise.resolve(ok(["0x9"])); - case "address_name": - return Promise.resolve(ok(arguments_.address ?? null)); - case "xrefs": - return Promise.resolve(ok(["0x20", "0x21"])); - case "resolve_containing_procedure": - return Promise.resolve( - ok({ - query_address: - typeof arguments_.address === "string" - ? arguments_.address - : "0x0", - found: false, - procedure: null, - reason: "not_in_procedure", - }), - ); - case "list_segments": - return Promise.resolve( - ok([{ name: "__TEXT", start: "0x1000", end: "0x2000" }]), - ); - case "list_documents": - return Promise.resolve(ok(["fixture"])); - case "list_strings": - return Promise.resolve(ok(inventory({ "0x30": "hello" }))); - case "search_strings": - case "search_procedures": - return Promise.resolve(ok(inventory({ "0x1": "needle" }))); - case "analyze_function": - return Promise.resolve( - ok({ - procedure: { - address: "0x1", - name: "entry", - signature: null, - locals: [], - }, - pseudocode: { - text: "return 0;", - total_chars: 9, - returned_chars: 9, - truncated: false, - next_offset: null, - }, - assembly: { - items: [], - total: 0, - returned: 0, - truncated: false, - next_offset: null, - }, - comments: emptyBounded(), - callers: emptyBounded(), - callees: emptyBounded(), - incoming_references: emptyBounded(), - outgoing_references: emptyBounded(), - referenced_strings: emptyBounded(), - referenced_names: emptyBounded(), - basic_blocks: emptyBounded(), - instruction_scan: { scanned: 0, truncated: false }, - }), - ); - default: - return Promise.resolve(ok(null)); - } - }, -}); - -const resources: Array<{ close(): Promise }> = []; - -afterEach(async () => { - await Promise.all( - resources.splice(0).map(async (resource) => resource.close()), - ); -}); - -const connect = async (analysis: AnalysisOperationPort = fixturePort()) => { - const server = createServer(analysis); - const client = new Client({ name: "enhanced-test", version: "1.0.0" }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - resources.push(client, server); - await server.connect(serverTransport); - await client.connect(clientTransport); - return client; -}; +afterEach(closeEnhancedToolResources); describe("enhanced MCP input validation", () => { it("lets the SDK reject misspelled tool inputs", async () => { - const client = await connect(); + const client = await connect({ execute: () => Promise.resolve(ok([])) }); const valid = await client.callTool({ name: "trace_feature", arguments: { query: "needle" }, diff --git a/tests/boundary/mcp/enhancedToolsHarness.ts b/tests/boundary/mcp/enhancedToolsHarness.ts index b4d2b3a6c..5c4af15dc 100644 --- a/tests/boundary/mcp/enhancedToolsHarness.ts +++ b/tests/boundary/mcp/enhancedToolsHarness.ts @@ -6,7 +6,6 @@ import { import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import type { AnalysisOperationPort } from "../../../src/application/AnalysisProvider.js"; import { createServer } from "../../../src/server/createServer.js"; -import { observed as ok } from "../../fixtures/analysisExecution.js"; export const PROCEDURES = { "0x1": "_TtC7Fixture5Class", @@ -20,119 +19,9 @@ export const PROCEDURES = { export const inventory = (values: Readonly>) => Object.entries(values).map(([address, value]) => ({ address, value })); -export const fixturePort = (): AnalysisOperationPort => ({ - execute: (name, arguments_) => { - switch (name) { - case "list_procedures": - return Promise.resolve(ok(inventory(PROCEDURES))); - case "list_names": - return Promise.resolve( - ok( - inventory({ - "0x10": "_OBJC_CLASS_$_Fixture", - "0x11": "_OBJC_CLASS_$_Fixture", - "0x12": "_OBJC_PROTOCOL_$_FixtureDelegate", - "0x13": "entry", - }), - ), - ); - case "procedure_pseudo_code": { - const procedure = arguments_.procedure; - return Promise.resolve( - ok(typeof procedure === "string" ? `pseudo:${procedure}` : "invalid"), - ); - } - case "procedure_callees": { - const procedure = arguments_.procedure; - return Promise.resolve( - ok( - procedure === "0x1" - ? ["0x2", "0x3"] - : procedure === "0x2" - ? ["0x1"] - : [], - ), - ); - } - case "procedure_callers": - return Promise.resolve(ok(["0x9"])); - case "address_name": - return Promise.resolve(ok(arguments_.address ?? null)); - case "xrefs": - return Promise.resolve(ok(["0x20", "0x21"])); - case "resolve_containing_procedure": - return Promise.resolve( - ok({ - query_address: - typeof arguments_.address === "string" - ? arguments_.address - : "0x0", - found: false, - procedure: null, - reason: "not_in_procedure", - }), - ); - case "list_segments": - return Promise.resolve( - ok([{ name: "__TEXT", start: "0x1000", end: "0x2000" }]), - ); - case "list_documents": - return Promise.resolve(ok(["fixture"])); - case "list_strings": - return Promise.resolve(ok(inventory({ "0x30": "hello" }))); - case "analyze_function": - return Promise.resolve( - ok({ - procedure: { - address: "0x1", - name: "entry", - signature: null, - locals: [], - }, - pseudocode: { - text: "return 0;", - total_chars: 9, - returned_chars: 9, - truncated: false, - next_offset: null, - }, - assembly: { - items: [], - total: 0, - returned: 0, - truncated: false, - next_offset: null, - }, - comments: emptyBounded(), - callers: emptyBounded(), - callees: emptyBounded(), - incoming_references: emptyBounded(), - outgoing_references: emptyBounded(), - referenced_strings: emptyBounded(), - referenced_names: emptyBounded(), - basic_blocks: emptyBounded(), - instruction_scan: { scanned: 0, truncated: false }, - }), - ); - default: - return Promise.resolve(ok(null)); - } - }, -}); - -export const emptyBounded = () => ({ - items: [], - total: 0, - returned: 0, - truncated: false, - next_offset: null, -}); - export const resources: Array<{ close(): Promise }> = []; -export const connect = async ( - analysis: AnalysisOperationPort = fixturePort(), -) => { +export const connect = async (analysis: AnalysisOperationPort) => { const server = createServer(analysis); const client = new Client({ name: "enhanced-test", version: "1.0.0" }); const [clientTransport, serverTransport] = diff --git a/tests/boundary/mcp/mainErrors.test.ts b/tests/boundary/mcp/mainErrors.test.ts index 3625b1974..0b3e3ce44 100644 --- a/tests/boundary/mcp/mainErrors.test.ts +++ b/tests/boundary/mcp/mainErrors.test.ts @@ -124,28 +124,6 @@ describe("MCP runtime errors", () => { ]); }); - it("starts the core transport when optional providers fail to load", async () => { - const output: string[] = []; - let serveCalls = 0; - expect( - await run( - dependencies({ - serve: () => { - serveCalls += 1; - return { close: () => Promise.resolve() }; - }, - output, - shutdown: [], - exitCodes: [], - loadOptionalProviders: () => - Promise.reject(new Error("synthetic optional import failure")), - }), - ), - ).toBe(0); - expect(serveCalls).toBe(1); - expect(output).toEqual([]); - }); - it("reports shutdown failure and sets a failing exit code", async () => { const output: string[] = []; const shutdown: Array<() => void> = []; diff --git a/tests/boundary/mcp/mainReload.test.ts b/tests/boundary/mcp/mainReload.test.ts index ed5a65c35..e101cf609 100644 --- a/tests/boundary/mcp/mainReload.test.ts +++ b/tests/boundary/mcp/mainReload.test.ts @@ -8,7 +8,7 @@ import type { RuntimeDependencies } from "../../../src/main/types.js"; import { createRuntimeState } from "../../../src/main/state.js"; describe("runtime configuration reload", () => { - it("applies a valid configuration reload", () => { + it("applies valid reloads and keeps the last valid config after an invalid reload", () => { const env: NodeJS.ProcessEnv = { REA_LOG_LEVEL: "info" }; const runtime = setupReload(env); @@ -16,16 +16,11 @@ describe("runtime configuration reload", () => { runtime.reload(); expect(runtime.state.currentConfig.logLevel).toBe("debug"); - }); - - it("retains the last valid configuration after an invalid reload", () => { - const env: NodeJS.ProcessEnv = { REA_LOG_LEVEL: "info" }; - const runtime = setupReload(env); env.REA_LOG_LEVEL = "invalid"; runtime.reload(); - expect(runtime.state.currentConfig.logLevel).toBe("info"); + expect(runtime.state.currentConfig.logLevel).toBe("debug"); }); }); diff --git a/tests/boundary/mcp/managedMcp.test.ts b/tests/boundary/mcp/managedMcp.test.ts index 4ededb36d..f54aa37ac 100644 --- a/tests/boundary/mcp/managedMcp.test.ts +++ b/tests/boundary/mcp/managedMcp.test.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import type { CallToolResult } from "@modelcontextprotocol/server"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { expect, it } from "vitest"; import { z } from "zod"; @@ -14,16 +15,21 @@ import type { BinarySession } from "../../../src/application/binary/BinarySessio import type { BinaryTarget } from "../../../src/domain/binaryTarget.js"; import { SessionProviderRouter } from "../../../src/application/binary/SessionProviderRouter.js"; import { MANAGED_NATIVE_VERIFICATION_EXAMPLE } from "../../../src/contracts/managed/managedWorkflowExamples.js"; +import { toolContract } from "../../../src/contracts/toolContracts.js"; import { ManagedStaticProvider } from "../../../src/dotnet/ManagedStaticProvider.js"; import { createServer } from "../../../src/server/createServer.js"; import { buildManagedPeFixture } from "../../../src/dotnet/ManagedPe.fixture.js"; +type ManagedOutputAssertion = Awaited>; + it("runs every managed static inspection independently of an active native target", async () => { const directory = await createTestTempDirectory( "rea-managed-independent-mcp-", ); const path = join(directory, "fixture.dll"); + const otherPath = join(directory, "other-fixture.dll"); await writeFile(path, buildManagedPeFixture()); + await writeFile(otherPath, buildManagedPeFixture({ methodName: "Other" })); const session = composeBinarySession( SessionProviderRouter.selectable(new AnalysisProviderRegistry([]), [ new ManagedStaticProvider(), @@ -50,6 +56,12 @@ it("runs every managed static inspection independently of an active native targe try { await server.connect(serverTransport); await client.connect(clientTransport); + const managedMembersTool = (await client.listTools()).tools.find( + ({ name }) => name === "inspect_managed_members", + ); + expect(managedMembersTool?.inputSchema).toMatchObject({ + additionalProperties: false, + }); const artifact = structured( await client.callTool({ name: "inspect_managed_artifact", @@ -63,6 +75,11 @@ it("runs every managed static inspection independently of an active native targe subject: { local_path: path, format: "pe" }, }, }); + const misspelledPath = await client.callTool({ + name: "inspect_managed_members", + arguments: { pth: otherPath }, + }); + expect(misspelledPath.isError).toBe(true); const invalidSelection = await client.callTool({ name: "inspect_managed_artifact", arguments: { path: join(directory, "missing.dll") }, @@ -78,6 +95,24 @@ it("runs every managed static inspection independently of an active native targe subject: { local_path: path, format: "pe" }, }, }); + const otherArtifact = structured( + await client.callTool({ + name: "inspect_managed_artifact", + arguments: { path: otherPath }, + }), + ); + expect(otherArtifact).toMatchObject({ + evidence: { subject: { local_path: otherPath, format: "pe" } }, + }); + const reusedOtherTarget = structured( + await client.callTool({ name: "inspect_managed_members", arguments: {} }), + ); + expect(reusedOtherTarget).toMatchObject({ + evidence: { + operation: "inspect_managed_members", + subject: { local_path: otherPath, format: "pe" }, + }, + }); const boundaries = structured( await client.callTool({ name: "inspect_managed_native_boundaries", @@ -126,9 +161,19 @@ it("opens a managed PE and executes the managed static provider through MCP", as try { await server.connect(serverTransport); await client.connect(clientTransport); - await verifyManagedCatalogAndNativeWorkflow(client, session); - const members = await inspectManagedStaticWorkflow(client, path); - await verifyManagedComparisonAndReconstruction(client, members, rightPath); + const assertOutput = await createOutputAssertion(client); + await verifyManagedCatalogAndNativeWorkflow(client, session, assertOutput); + const members = await inspectManagedStaticWorkflow( + client, + path, + assertOutput, + ); + await verifyManagedComparisonAndReconstruction( + client, + members, + rightPath, + assertOutput, + ); } finally { await Promise.all([client.close(), server.close()]); await session.close(); @@ -138,9 +183,9 @@ it("opens a managed PE and executes the managed static provider through MCP", as const verifyManagedCatalogAndNativeWorkflow = async ( client: Client, session: BinarySession, + outputAssertion: ManagedOutputAssertion, ): Promise => { - const names = (await client.listTools()).tools.map(({ name }) => name); - expect(names).toEqual( + expect(outputAssertion.names).toEqual( expect.arrayContaining([ "inspect_managed_artifact", "inspect_managed_members", @@ -166,6 +211,7 @@ const verifyManagedCatalogAndNativeWorkflow = async ( }, }), ); + await outputAssertion.assert("verify_managed_native_boundaries", verified); expect(verified).toMatchObject({ evidence_id: expect.stringMatching(/^ev_[a-f0-9]{64}$/u), result: { @@ -195,6 +241,7 @@ const verifyManagedCatalogAndNativeWorkflow = async ( const inspectManagedStaticWorkflow = async ( client: Client, path: string, + outputAssertion: ManagedOutputAssertion, ): Promise> => { const inspected = structured( await client.callTool({ @@ -202,6 +249,7 @@ const inspectManagedStaticWorkflow = async ( arguments: { path }, }), ); + await outputAssertion.assert("inspect_managed_artifact", inspected); expect(inspected).toMatchObject({ result: { classification: { status: "managed", runtime_family: "modern-dotnet" }, @@ -221,6 +269,11 @@ const inspectManagedStaticWorkflow = async ( }), ), ); + await outputAssertion.assert("inspect_managed_members", { + result: members.normalized_result, + evidence_id: members.evidence_id, + evidence: members, + }); expect(members).toMatchObject({ operation: "inspect_managed_members", provider: { id: "rea-dotnet-static" }, @@ -242,6 +295,7 @@ const inspectManagedStaticWorkflow = async ( arguments: { path }, }), ); + await outputAssertion.assert("inspect_managed_native_boundaries", boundaries); expect(boundaries).toMatchObject({ result: { identity_scope: { token_identity: "build-local" }, @@ -292,6 +346,7 @@ const verifyManagedComparisonAndReconstruction = async ( client: Client, members: Record, rightPath: string, + outputAssertion: ManagedOutputAssertion, ): Promise => { await client.callTool({ name: "open_binary", @@ -316,6 +371,11 @@ const verifyManagedComparisonAndReconstruction = async ( }), ), ); + await outputAssertion.assert("compare_managed_members", { + result: compared.normalized_result, + evidence_id: compared.evidence_id, + evidence: compared, + }); expect(compared).toMatchObject({ operation: "compare_managed_members", provider: { id: "rea-dotnet-workflows" }, @@ -394,6 +454,31 @@ const structured = (result: CallToolResult): Record => { return z.record(z.string(), z.unknown()).parse(result.structuredContent); }; +const createOutputAssertion = async (client: Client) => { + const tools = (await client.listTools()).tools; + const ajv = new Ajv2020({ + strict: false, + validateFormats: false, + }); + return { + names: tools.map(({ name }) => name), + assert: ( + name: Parameters[0], + output: Record, + ): void => { + const contract = toolContract(name); + const wire = tools.find((tool) => tool.name === name); + if (wire?.outputSchema === undefined) + throw new Error(`Missing advertised output schema for ${name}`); + const valid = ajv.compile( + z.record(z.string(), z.unknown()).parse(wire.outputSchema), + ); + expect(valid(output), `${name} advertised output`).toBe(true); + expect(contract.outputSchema.safeParse(output).success).toBe(true); + }, + }; +}; + const inlineEvidence = ( value: Readonly>, ): Record => { diff --git a/tests/boundary/mcp/mcpStartupPolicy.test.ts b/tests/boundary/mcp/mcpStartupPolicy.test.ts index ce3706670..da38caa19 100644 --- a/tests/boundary/mcp/mcpStartupPolicy.test.ts +++ b/tests/boundary/mcp/mcpStartupPolicy.test.ts @@ -1,25 +1,11 @@ import { describe, expect, it } from "vitest"; -import { MCP_STARTUP_POLICY } from "../../../src/mcpStartupPolicy.js"; import { createEsmModuleProfileCollector, parseLinuxVmRssBytes, } from "../../../scripts/lib/mcp-startup-probe.mjs"; describe("MCP startup policy", () => { - it("preserves headroom beneath the configured Codex timeout", () => { - const codexTimeoutMs = - MCP_STARTUP_POLICY.codexStartupTimeoutSeconds * 1_000; - - expect(MCP_STARTUP_POLICY.initializeBudgetMs).toBeLessThan(codexTimeoutMs); - expect(MCP_STARTUP_POLICY.firstCatalogBudgetMs).toBeLessThan( - codexTimeoutMs, - ); - expect(MCP_STARTUP_POLICY.doctorDeadlineMs).toBeGreaterThanOrEqual( - codexTimeoutMs, - ); - }); - it("parses Linux resident memory without accepting other fields", () => { expect( parseLinuxVmRssBytes("Name:\tnode\nVmRSS:\t 12345 kB\nThreads:\t7\n"), diff --git a/tests/boundary/mcp/nativeInputContracts.test.ts b/tests/boundary/mcp/nativeInputContracts.test.ts new file mode 100644 index 000000000..4429f8863 --- /dev/null +++ b/tests/boundary/mcp/nativeInputContracts.test.ts @@ -0,0 +1,119 @@ +import { Ajv2020 } from "ajv/dist/2020.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { afterEach, describe, expect, it } from "vitest"; + +import type { AnalysisOperationPort } from "../../../src/application/AnalysisProvider.js"; +import { NATIVE_TOOL_CONTRACTS } from "../../../src/contracts/native/nativeToolContracts.js"; +import type { JsonValue } from "../../../src/domain/jsonValue.js"; +import { ok } from "../../../src/domain/result.js"; +import { createAnalysisExecution } from "../../../src/application/AnalysisProvider.js"; +import { createServer } from "../../../src/server/createServer.js"; + +const provider = { + id: "fixture-native", + name: "Fixture native provider", + version: "1", +} as const; + +const plistResult = { + format: "xml", + value: {}, + bundle: { + identifier: null, + executable: null, + name: null, + version: null, + short_version: null, + }, + source_path: "/Applications/Fixture.app/Contents/Info.plist", + provenance: [], + limitations: [], +}; + +const resources: Array<{ close(): Promise }> = []; + +const connect = async ( + execute: AnalysisOperationPort["execute"], +): Promise => { + const server = createServer({ execute }); + const client = new Client({ name: "native-contract-test", version: "1" }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + resources.push(client, server); + await server.connect(serverTransport); + await client.connect(clientTransport); + return client; +}; + +afterEach(async () => { + await Promise.all( + resources.splice(0).map(async (resource) => resource.close()), + ); +}); + +describe("native MCP input contracts", () => { + it("rejects a misspelled plist path before dispatch and preserves omission", async () => { + const calls: Array<{ + readonly operation: string; + readonly parameters: Readonly>; + }> = []; + const client = await connect((operation, parameters) => { + calls.push({ operation, parameters }); + return Promise.resolve( + ok(createAnalysisExecution(plistResult, provider)), + ); + }); + + const tools = new Map( + (await client.listTools()).tools.map((tool) => [tool.name, tool]), + ); + for (const nativeContract of NATIVE_TOOL_CONTRACTS) + expect(tools.get(nativeContract.name)?.inputSchema).toHaveProperty( + "additionalProperties", + false, + ); + + const contract = NATIVE_TOOL_CONTRACTS.find( + ({ name }) => name === "inspect_plist", + ); + const advertised = tools.get("inspect_plist"); + if (contract === undefined || advertised === undefined) + throw new Error("inspect_plist was not registered"); + + const validate = new Ajv2020({ + strict: false, + validateFormats: false, + }).compile(advertised.inputSchema); + const typoInput = { pth: "/tmp/Other.plist" }; + expect(validate(typoInput)).toBe(false); + expect(contract.inputSchema.safeParse(typoInput).success).toBe(false); + expect(validate({})).toBe(true); + expect(contract.inputSchema.safeParse({}).success).toBe(true); + + const defaulted = await client.callTool({ + name: "inspect_plist", + arguments: {}, + }); + expect(defaulted.isError).not.toBe(true); + expect(calls[0]).toEqual({ operation: "inspect_plist", parameters: {} }); + + const selectedPath = "/tmp/Other.plist"; + const explicit = await client.callTool({ + name: "inspect_plist", + arguments: { path: selectedPath }, + }); + expect(explicit.isError).not.toBe(true); + expect(calls[1]).toEqual({ + operation: "inspect_plist", + parameters: { path: selectedPath }, + }); + + const beforeTypo = calls.length; + const rejected = await client.callTool({ + name: "inspect_plist", + arguments: typoInput, + }); + expect(rejected.isError).toBe(true); + expect(calls).toHaveLength(beforeTypo); + }); +}); diff --git a/tests/boundary/mcp/officialTools.test.ts b/tests/boundary/mcp/officialTools.test.ts index ce95aa0c3..b7e4f0014 100644 --- a/tests/boundary/mcp/officialTools.test.ts +++ b/tests/boundary/mcp/officialTools.test.ts @@ -1,5 +1,4 @@ -import { functionDossierSchema } from "../../../src/domain/hopperValues.js"; -import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { afterEach, describe, expect, it } from "vitest"; @@ -11,52 +10,6 @@ import { observed as ok } from "../../fixtures/analysisExecution.js"; import type { JsonValue } from "../../../src/domain/jsonValue.js"; import { createServer } from "../../../src/server/createServer.js"; -const VALID_INPUTS: Readonly< - Record>> -> = { - annotate_native_function: { procedure: "0x1000", name: "entry" }, - inspect_native_load_image: {}, - address_name: {}, - inspect_native_instruction: { address: "0x1000" }, - resolve_native_call_targets: { address: "0x1000" }, - inspect_native_data_type: { type: "/Missing" }, - comment: {}, - current_address: {}, - current_procedure: {}, - current_document: {}, - goto_address: { address: "0x1000" }, - inline_comment: {}, - list_bookmarks: {}, - list_documents: {}, - list_names: {}, - list_procedures: {}, - list_segments: {}, - list_strings: {}, - next_address: {}, - prev_address: {}, - procedure_address: { procedure: "main" }, - procedure_assembly: { procedure: "main" }, - procedure_callees: { procedure: "main" }, - procedure_callers: { procedure: "main" }, - procedure_info: { procedure: "main" }, - read_function_instructions: { procedure: "main" }, - read_bytes: { address: "0x1000", length: 4 }, - address_to_file_offset: { address: "0x1000" }, - procedure_references: { procedure: "main" }, - procedure_pseudo_code: { procedure: "main" }, - resolve_containing_procedure: { address: "0x1000" }, - search_procedures: { pattern: "main" }, - search_strings: { pattern: "hello" }, - set_address_name: { address: "0x1000", name: "entry" }, - set_addresses_names: { names: { "0x1000": "entry" } }, - set_bookmark: { address: "0x1000" }, - set_comment: { address: "0x1000", comment: "entry point" }, - set_current_document: { document: "fixture" }, - set_inline_comment: { address: "0x1000", comment: "entry point" }, - unset_bookmark: { address: "0x1000" }, - xrefs: {}, -}; - interface Invocation { readonly name: string; readonly arguments_: Readonly>; @@ -128,204 +81,54 @@ const connect = async (analysis: AnalysisOperationPort) => { return client; }; -const inventory: JsonValue = []; - -const outputFor = (name: string): JsonValue => { - if (name === "annotate_native_function") { - const dossier = functionDossierSchema.parse(ghidraFunctionDossier()); - return { - annotations: { - address: dossier.procedure.address, - name: dossier.procedure.name, - comment: null, - inline_comment: null, - }, - dossier, - effects: { - scope: "session-analysis-database", - source_bytes_modified: false, - persists_after_close: false, - }, - }; - } - if (name === "inspect_native_load_image") - return { - status: "unsupported", - reason: "Fixture provider has no load-image verifier", - observations: { - executable_format: "ELF", - language_id: "x86:LE:64:default", - compiler_spec_id: "gcc", - image_base: "0x0", - default_address_space: "ram", - source_files: [], - mappings: [], - relocations: [], - entry_context: [], - entry_points: [], - }, - limitations: ["Transport fixture only"], - }; - if (name === "inspect_native_instruction") - return { - address: "0x1000", - status: "decoded", - procedure: "0x1000", - architecture: "x86:LE:64:default", - mode: "default", - bytes: "c3", - length: 1, - mnemonic: "RET", - raw_disassembly: "RET", - operands: [], - flow: { - kind: "terminal", - conditional: false, - computed: false, - direct_destinations: [], - }, - references: [], - limitations: ["Provider decoder observation"], - }; - if (name === "resolve_native_call_targets") - return { - call_site: "0x1000", - procedure: "0x1000", - status: "not-call", - mechanism: "unavailable", - targets: [], - limitations: [], - }; - if (name === "inspect_native_data_type") - return { - status: "unavailable", - reason: "Type absent", - id: null, - name: null, - kind: "unavailable", - source: "analysis-database", - source_archive: null, - address: null, - size_bytes: null, - alignment_bytes: null, - packing_enabled: null, - referenced_type: null, - array_count: null, - array_stride_bytes: null, - fields: [], - members: [], - total_fields: 0, - truncated: false, - limitations: [], - }; - if (["list_procedures", "list_names", "list_strings"].includes(name)) - return inventory; - if (["address_name", "comment", "inline_comment"].includes(name)) return null; - if (["procedure_callees", "procedure_callers", "xrefs"].includes(name)) - return []; - if (["list_bookmarks", "list_documents", "list_segments"].includes(name)) - return []; - if ( - [ - "set_address_name", - "set_bookmark", - "set_comment", - "set_inline_comment", - "unset_bookmark", - ].includes(name) - ) - return true; - if (name === "set_addresses_names") return { "0x1000": true }; - if (["search_procedures", "search_strings"].includes(name)) return inventory; - if (name === "procedure_info") - return { - name: "main", - entrypoint: "0x1000", - basicblock_count: 1, - length: 4, - signature: null, - locals: [], - }; - if (name === "resolve_containing_procedure") - return { - query_address: "0x1000", - found: true, - procedure: { address: "0x1000", name: "main" }, - }; - if (name === "read_function_instructions") - return { - procedure: { address: "0x1000", name: "main" }, - instructions: ["0x1000: ret"], - limitations: ["Provider-specific instruction text."], - }; - if (name === "read_bytes") - return { - address: "0x1000", - requested_bytes: 4, - returned_bytes: 4, - bytes_hex: "c3c3c3c3", - complete: true, - }; - if (name === "address_to_file_offset") - return { address: "0x1000", file_offset: 0 }; - if (name === "procedure_references") - return { - procedure: { address: "0x1000", name: "main" }, - direction: "outgoing", - references: [], - }; - return name.includes("address") ? "0x1000" : "fixture"; -}; - -describe("official Hopper proxy tools", () => { - it("executes every handler and projects omitted Python optionals to null", async () => { +describe("official tool input contracts", () => { + it("rejects misspelled top-level inputs before dispatch and keeps record keys open", async () => { const invocations: Invocation[] = []; const client = await connect({ execute: (name, arguments_) => { invocations.push({ name, arguments_ }); - return Promise.resolve(ok(outputFor(name))); + return Promise.resolve(ok(null)); }, }); - + const { tools } = await client.listTools(); + const advertisedByName = new Map(tools.map((tool) => [tool.name, tool])); for (const contract of OFFICIAL_TOOL_CONTRACTS) { - const result = await client.callTool({ - name: contract.name, - arguments: VALID_INPUTS[contract.name], - }); - expect(result.isError).not.toBe(true); + expect(advertisedByName.get(contract.name)?.inputSchema).toHaveProperty( + "additionalProperties", + false, + ); } + const advertised = tools.find(({ name }) => name === "address_name"); + const recordTool = tools.find(({ name }) => name === "set_addresses_names"); + if (advertised === undefined || recordTool === undefined) + throw new Error("Official tools were not advertised"); - expect( - invocations.find(({ name }) => name === "annotate_native_function") - ?.arguments_, - ).toEqual({ procedure: "0x1000", name: "entry" }); - expect(invocations.map(({ name }) => name)).toEqual( - OFFICIAL_TOOL_CONTRACTS.map(({ name }) => name), + const ajv = new Ajv2020({ strict: false, validateFormats: false }); + const validateAddressName = ajv.compile(advertised.inputSchema); + const validateRecordInput = ajv.compile(recordTool.inputSchema); + const typoInput = { adress: "0x1234" }; + expect(advertised.inputSchema).toHaveProperty( + "additionalProperties", + false, ); + expect(validateAddressName(typoInput)).toBe(false); expect( - invocations.find(({ name }) => name === "address_name")?.arguments_, - ).toEqual({ - document: null, - address: null, - }); - expect( - invocations.find(({ name }) => name === "search_procedures")?.arguments_, - ).toEqual({ - pattern: "main", - mode: "literal", - case_sensitive: false, - document: null, - }); - expect( - invocations.find(({ name }) => name === "search_strings")?.arguments_, - ).toEqual({ - pattern: "hello", - mode: "literal", - case_sensitive: false, - document: null, - }); - }); + OFFICIAL_TOOL_CONTRACTS.find( + ({ name }) => name === "address_name", + )?.inputSchema.safeParse(typoInput).success, + ).toBe(false); + expect(validateRecordInput({ names: { "0x1000": "entry" } })).toBe(true); + const result = await client.callTool({ + name: "address_name", + arguments: typoInput, + }); + expect(result.isError).toBe(true); + expect(invocations).toEqual([]); + }); +}); + +describe("official Hopper proxy tools", () => { it("returns stable safe MCP error content", async () => { const client = await connect({ execute: () => Promise.resolve(err(new HopperRemoteError(-1, "denied"))), @@ -341,7 +144,7 @@ describe("official Hopper proxy tools", () => { }); }); - it("returns every procedure inline without caller-authored page calls", async () => { + it("binds list_procedures and projects its omitted Python document option", async () => { const invocations: Invocation[] = []; const client = await connect({ execute: (name, arguments_) => { @@ -367,6 +170,7 @@ describe("official Hopper proxy tools", () => { ], }); expect(invocations).toHaveLength(1); + expect(invocations[0]?.name).toBe("list_procedures"); expect(invocations[0]?.arguments_).toEqual({ document: null }); expect( (await client.listTools()).tools.find( diff --git a/tests/boundary/mcp/outputSchemaSemanticConstraints.test.ts b/tests/boundary/mcp/outputSchemaSemanticConstraints.test.ts new file mode 100644 index 000000000..84d87dfea --- /dev/null +++ b/tests/boundary/mcp/outputSchemaSemanticConstraints.test.ts @@ -0,0 +1,166 @@ +import { createHash } from "node:crypto"; + +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { McpServer } from "@modelcontextprotocol/server"; +import { Ajv2020 } from "ajv/dist/2020.js"; +import { expect, it } from "vitest"; +import { z } from "zod"; + +import { toolContract } from "../../../src/contracts/toolContracts.js"; +import { MANAGED_NATIVE_VERIFICATION_EXAMPLE } from "../../../src/contracts/managed/managedWorkflowExamples.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js"; +import { + managedNativeVerificationInputSchema, + managedNativeVerificationResultSchema, + verifyManagedNativeBoundaries, +} from "../../../src/domain/managed/managedNativeVerification.js"; +import { nativeUiResultSchema } from "../../../src/domain/native/nativeUiObservation.js"; +import { processCaptureSchema } from "../../../src/domain/process/processCapture.js"; +import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js"; + +const record = (value: unknown): Record => { + if (typeof value !== "object" || value === null || Array.isArray(value)) + throw new Error("Expected a schema object"); + return value as Record; +}; + +it("advertises semantic output invariants and keeps producers aligned with Zod", async () => { + const contracts = [ + toolContract("capture_native_ui_scenario"), + toolContract("capture_process_scenario"), + toolContract("verify_managed_native_boundaries"), + ]; + const server = new McpServer({ + name: "output-schema-semantics", + version: "1", + }); + const client = new Client({ name: "output-schema-semantics", version: "1" }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + for (const contract of contracts) + server.registerTool( + contract.name, + toolRegistrationOptions(contract), + async () => ({ content: [] }), + ); + + try { + await server.connect(serverTransport); + await client.connect(clientTransport); + const advertised = new Map( + (await client.listTools()).tools.map((tool) => [tool.name, tool]), + ); + const outputResultSchema = (name: (typeof contracts)[number]["name"]) => { + const tool = advertised.get(name); + if (tool?.outputSchema === undefined) + throw new Error(`${name} output schema missing`); + const outputSchema = record(tool.outputSchema); + const result = record(record(outputSchema.properties).result); + const standaloneResultSchema = z.record(z.string(), z.unknown()).parse({ + ...result, + ...(typeof outputSchema.$schema === "string" + ? { $schema: outputSchema.$schema } + : {}), + ...(outputSchema.$defs === undefined + ? {} + : { $defs: outputSchema.$defs }), + }); + return { + result, + validate: new Ajv2020({ + strict: false, + validateFormats: false, + }).compile(standaloneResultSchema), + }; + }; + + const native = outputResultSchema("capture_native_ui_scenario"); + const initial = record(record(native.result.properties).initial); + const screenshotUnion = record(record(initial.properties).screenshot); + const screenshotVariants = screenshotUnion.anyOf; + if (!Array.isArray(screenshotVariants)) + throw new Error("Expected nullable screenshot schema"); + const screenshotProperties = record( + record(screenshotVariants[0]).properties, + ); + expect(record(screenshotProperties.base64).description).toContain( + "PNG signature", + ); + expect(record(screenshotProperties.sha256).description).toContain( + "decoded PNG bytes", + ); + const pngBytes = Buffer.from("not a PNG"); + const invalidScreenshot = { + target_sha256: "a".repeat(64), + initial: { + window: { + pid: 1, + window_id: 1, + executable: "app", + launch_time: 0, + title: "", + }, + nodes: [], + truncated: false, + screenshot: { + mime_type: "image/png", + base64: pngBytes.toString("base64"), + sha256: createHash("sha256").update(pngBytes).digest("hex"), + width: 1, + height: 1, + }, + gaps: [], + }, + steps: [], + restore: "leave-as-is", + limitations: [], + }; + expect(native.validate(invalidScreenshot)).toBe(true); + expect(nativeUiResultSchema.safeParse(invalidScreenshot).success).toBe( + false, + ); + + const process = outputResultSchema("capture_process_scenario"); + expect(process.result.description).toContain("canonical scenario"); + expect(process.result.description).toContain( + "older input without an event journal", + ); + const validCapture = processCaptureSchema.parse( + EMPTY_PROCESS_CAPTURE_EXAMPLE, + ); + expect(validCapture.event_journal).toEqual([]); + expect(process.validate(validCapture)).toBe(true); + expect( + processCaptureSchema.safeParse(EMPTY_PROCESS_CAPTURE_EXAMPLE).success, + ).toBe(true); + const invalidCapture = { + ...validCapture, + manifest: { + ...validCapture.manifest, + full_scenario_sha256: "0".repeat(64), + }, + }; + expect(process.validate(invalidCapture)).toBe(true); + expect(processCaptureSchema.safeParse(invalidCapture).success).toBe(false); + + const managed = outputResultSchema("verify_managed_native_boundaries"); + expect(managed.result.description).toContain( + "accepted plus unsupported equals total", + ); + const validVerification = verifyManagedNativeBoundaries( + managedNativeVerificationInputSchema.parse( + MANAGED_NATIVE_VERIFICATION_EXAMPLE, + ), + ); + expect(managed.validate(validVerification)).toBe(true); + const invalidVerification = structuredClone(validVerification); + invalidVerification.summary.verified += 1; + expect(managed.validate(invalidVerification)).toBe(true); + expect( + managedNativeVerificationResultSchema.safeParse(invalidVerification) + .success, + ).toBe(false); + } finally { + await Promise.allSettled([client.close(), server.close()]); + } +}); diff --git a/tests/boundary/mcp/processEvidenceIdentityMcp.test.ts b/tests/boundary/mcp/processEvidenceIdentityMcp.test.ts index 2567976fa..271ca2a7a 100644 --- a/tests/boundary/mcp/processEvidenceIdentityMcp.test.ts +++ b/tests/boundary/mcp/processEvidenceIdentityMcp.test.ts @@ -5,14 +5,14 @@ import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { expect, it, onTestFinished } from "vitest"; import { z } from "zod"; -import { compareProcessEvidenceFiles } from "../../../src/application/ProcessCli.js"; -import { PROCESS_PROVIDER } from "../../../src/application/ProcessEvidence.js"; +import { compareProcessEvidenceFiles } from "../../../src/application/process/ProcessCli.js"; +import { PROCESS_PROVIDER } from "../../../src/application/process/ProcessEvidence.js"; import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js"; import { FUNCTION_COMPARISON_EVIDENCE, INVESTIGATION_EXAMPLES, } from "../../../src/contracts/investigationExamples.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/processCaptureExample.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/process/processCaptureExample.js"; import { findChangedBehavior } from "../../../src/domain/changedBehavior.js"; import { createEvidence, diff --git a/tests/boundary/mcp/processTraceAdaptersParity.test.ts b/tests/boundary/mcp/processTraceAdaptersParity.test.ts index 64b45a220..d27f37e84 100644 --- a/tests/boundary/mcp/processTraceAdaptersParity.test.ts +++ b/tests/boundary/mcp/processTraceAdaptersParity.test.ts @@ -6,12 +6,12 @@ import { afterEach, describe, expect, it } from "vitest"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; import type { AnalysisClient } from "../../../src/application/AnalysisProvider.js"; -import { compareProcessEvidenceFiles } from "../../../src/application/ProcessCli.js"; -import { PROCESS_PROVIDER } from "../../../src/application/ProcessEvidence.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/processCaptureExample.js"; +import { compareProcessEvidenceFiles } from "../../../src/application/process/ProcessCli.js"; +import { PROCESS_PROVIDER } from "../../../src/application/process/ProcessEvidence.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/process/processCaptureExample.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { jsonValueSchema } from "../../../src/domain/jsonValue.js"; -import type { ProcessTraceSpecification } from "../../../src/domain/processTraceComparison.js"; +import type { ProcessTraceSpecification } from "../../../src/domain/process/processTraceComparison.js"; import { createServer } from "../../../src/server/createServer.js"; import { observed as ok } from "../../fixtures/analysisExecution.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; diff --git a/tests/boundary/mcp/promptCompletion.test.ts b/tests/boundary/mcp/promptCompletion.test.ts index f1807bc99..8b8b31dbb 100644 --- a/tests/boundary/mcp/promptCompletion.test.ts +++ b/tests/boundary/mcp/promptCompletion.test.ts @@ -8,6 +8,7 @@ import type { AnalysisClient } from "../../../src/application/AnalysisProvider.j import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { ARTIFACT_COMPARISON_EXAMPLE } from "../../../src/contracts/artifactComparisonExample.js"; import { PROCESS_CAPTURE_REFERENCE } from "../../../src/contracts/investigationExamples.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js"; import { createEvidence } from "../../../src/domain/evidence.js"; import { createArtifactInspection } from "../../../src/domain/artifactInspection.js"; import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js"; @@ -57,6 +58,36 @@ describe("guided prompt completion from live analysis", () => { }); describe("guided prompt completion from investigation records", () => { + it("completes process captures imported in the original v3 format", async () => { + const session = createTestBinarySession(() => client([])); + const { + selected_executable_sha256: _selectedDigest, + executable_identity: _identity, + executable_sha256, + ...legacyManifest + } = EMPTY_PROCESS_CAPTURE_EXAMPLE.manifest; + const legacyCapture = { + ...EMPTY_PROCESS_CAPTURE_EXAMPLE, + manifest: { ...legacyManifest, executable_sha256 }, + }; + const evidence = createEvidence(undefined, fixtureProvider, { + operation: "capture_process_scenario", + parameters: {}, + result: legacyCapture, + confidence: "observed", + authority: "controlled-replay", + environment: fixtureEnvironment, + }); + expect(session.recordEvidence(evidence).ok).toBe(true); + const completion = createPromptCompletionSource(session, session); + + expect( + await completion.complete("capture", evidence.evidence_id.slice(0, 8)), + ).toEqual([evidence.evidence_id]); + + await session.close(); + }); + it("projects only validated typed identifiers from the evidence ledger", async () => { const session = createTestBinarySession(() => client([])); const invalidCapture = createEvidence(undefined, fixtureProvider, { diff --git a/tests/boundary/mcp/serverUnknownsMcp.test.ts b/tests/boundary/mcp/serverUnknownsMcp.test.ts index 57000324c..c08aedfd1 100644 --- a/tests/boundary/mcp/serverUnknownsMcp.test.ts +++ b/tests/boundary/mcp/serverUnknownsMcp.test.ts @@ -15,8 +15,8 @@ import { observed as ok } from "../../fixtures/analysisExecution.js"; import { createServer } from "../../../src/server/createServer.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; import { parseEvidenceBundle } from "../../../src/domain/evidenceBundle.js"; -import { processCaptureSchema } from "../../../src/domain/processCapture.js"; -import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/processCaptureExample.js"; +import { processCaptureSchema } from "../../../src/domain/process/processCapture.js"; +import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/process/processCaptureExample.js"; import { jsonValueSchema } from "../../../src/domain/jsonValue.js"; import { PROCESS_PROVIDER } from "../../../src/server/sessionToolPolicies.js"; diff --git a/tests/boundary/mcp/sessionMcp.test.ts b/tests/boundary/mcp/sessionMcp.test.ts index 637f00a8f..cc392308e 100644 --- a/tests/boundary/mcp/sessionMcp.test.ts +++ b/tests/boundary/mcp/sessionMcp.test.ts @@ -3,6 +3,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import type { CallToolResult } from "@modelcontextprotocol/server"; +import { Ajv2020 } from "ajv/dist/2020.js"; import { afterEach, describe, expect, it } from "vitest"; import { z } from "zod"; @@ -14,9 +15,10 @@ import type { AnalysisProvider, CapabilityDescriptor, } from "../../../src/application/AnalysisProvider.js"; -import { probeProcessCaptureCapability } from "../../../src/application/ProcessHarness.js"; +import { probeProcessCaptureCapability } from "../../../src/process/capture/ProcessHarness.js"; import { observed as ok } from "../../fixtures/analysisExecution.js"; import { createServer } from "../../../src/server/createServer.js"; +import { toolContract } from "../../../src/contracts/toolContracts.js"; import { silentLogger } from "../../../src/logger.js"; import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js"; import { ok as resultOk } from "../../../src/domain/result.js"; @@ -227,6 +229,37 @@ describe("target-free MCP workflow", () => { }, 10_000); }); +describe("session filesystem path boundaries over MCP", () => { + it("rejects relative snapshot and export paths with an absolute-path error", async () => { + directory = await createTestTempDirectory("rea-mcp-path-boundary-"); + const { mcp, first } = await createSessionMcpHarness( + directory, + provider, + resources, + ); + + const exported = await mcp.callTool({ + name: "export_evidence_bundle", + arguments: { path: "relative-bundle.json" }, + }); + expect(exported.isError, JSON.stringify(exported.content)).toBe(true); + expect(JSON.stringify(exported.content)).toContain("absolute"); + + const opened = await mcp.callTool({ + name: "open_binary", + arguments: { path: first }, + }); + expect(opened.isError, JSON.stringify(opened.content)).not.toBe(true); + const closed = await mcp.callTool({ + name: "close_binary", + arguments: { snapshot_path: "relative-analysis.json" }, + }); + expect(closed.isError, JSON.stringify(closed.content)).toBe(true); + expect(JSON.stringify(closed.content)).toContain("absolute"); + await mcp.callTool({ name: "close_binary", arguments: {} }); + }, 10_000); +}); + describe("process residuals over MCP", () => { it("records process residuals linked to capture Evidence", async () => { if (!(await probeProcessCaptureCapability()).available) return; @@ -249,6 +282,21 @@ describe("process residuals over MCP", () => { }, }); expect(captured.isError, text(captured)).not.toBe(true); + const contract = toolContract("capture_process_scenario"); + const wire = (await mcp.listTools()).tools.find( + ({ name }) => name === contract.name, + ); + if (wire?.outputSchema === undefined) + throw new Error("Missing process capture output schema"); + expect( + new Ajv2020({ strict: false, validateFormats: false }).validate( + z.record(z.string(), z.unknown()).parse(wire.outputSchema), + captured.structuredContent, + ), + ).toBe(true); + expect( + contract.outputSchema.safeParse(captured.structuredContent).success, + ).toBe(true); const listedUnknowns = await mcp.callTool({ name: "list_unknowns", arguments: {}, diff --git a/tests/boundary/mcp/toolRegistrationOptions.test.ts b/tests/boundary/mcp/toolRegistrationOptions.test.ts deleted file mode 100644 index af600d2e8..000000000 --- a/tests/boundary/mcp/toolRegistrationOptions.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; -import { McpServer } from "@modelcontextprotocol/server"; - -import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js"; -import { GENERATED_MCP_TOOL_CATALOG } from "../../../src/generatedMcpToolCatalog.js"; -import { annotationsFromEffects } from "../../../src/contracts/toolEffects.js"; -import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js"; - -describe("tool registration options", () => { - it("generates the checked-in catalog from the SDK wire projection", async () => { - const server = new McpServer({ name: "catalog-test", version: "0" }); - for (const contract of TOOL_CONTRACTS) - server.registerTool( - contract.name, - toolRegistrationOptions(contract), - async () => ({ - content: [{ type: "text" as const, text: "catalog-only" }], - isError: true as const, - }), - ); - const client = new Client({ name: "catalog-test", version: "0" }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - try { - await server.connect(serverTransport); - await client.connect(clientTransport); - const advertised = (await client.listTools()).tools.map((tool) => ({ - name: tool.name, - title: tool.title, - description: tool.description, - inputSchema: tool.inputSchema, - outputSchema: tool.outputSchema, - annotations: tool.annotations, - })); - for (const contract of TOOL_CONTRACTS) { - const tool = advertised.find(({ name }) => name === contract.name); - expect(tool, contract.name).toBeDefined(); - expect(describesObject(tool?.inputSchema), contract.name).toBe(true); - expect(describesObject(tool?.outputSchema), contract.name).toBe(true); - expect(tool?.title?.trim().length, contract.name).toBeGreaterThan(0); - expect(tool?.description?.trim().length, contract.name).toBeGreaterThan( - 0, - ); - expect(JSON.stringify(tool?.outputSchema), contract.name).not.toContain( - '"result":{}', - ); - expect(tool?.annotations, contract.name).toEqual( - annotationsFromEffects(contract.effects), - ); - expect(contract.examples.length, contract.name).toBeGreaterThan(0); - for (const example of contract.examples) { - expect(example.title.trim().length, contract.name).toBeGreaterThan(0); - expect( - contract.inputSchema.safeParse(example.input).success, - `${contract.name}: ${example.title}`, - ).toBe(true); - } - - expect(tool?.inputSchema.examples, contract.name).toEqual( - contract.examples.map(({ input }) => input), - ); - expect( - missingPropertyDescriptions(tool?.inputSchema), - contract.name, - ).toEqual([]); - } - expect(advertised).toEqual( - GENERATED_MCP_TOOL_CATALOG.map((tool) => ({ - name: tool.name, - title: tool.title, - description: tool.description, - inputSchema: tool.inputSchema, - outputSchema: tool.outputSchema, - annotations: tool.annotations, - })), - ); - for (const [name, expected] of [ - ["binary_session", { readOnlyHint: true, openWorldHint: false }], - ["read_bytes", { readOnlyHint: false, destructiveHint: false }], - ["set_comment", { readOnlyHint: false }], - ["unset_bookmark", { destructiveHint: true }], - [ - "capture_process_scenario", - { readOnlyHint: false, openWorldHint: true }, - ], - ["inspect_artifact", { readOnlyHint: false, openWorldHint: true }], - ["extract_artifact", { readOnlyHint: false, idempotentHint: false }], - ["compare_web_captures", { readOnlyHint: false, openWorldHint: false }], - [ - "compare_web_screenshots", - { readOnlyHint: false, openWorldHint: false }, - ], - ] as const) { - expect( - advertised.find((tool) => tool.name === name)?.annotations, - name, - ).toMatchObject(expected); - } - } finally { - await Promise.allSettled([client.close(), server.close()]); - } - }); -}); - -const missingPropertyDescriptions = ( - value: unknown, - path = "inputSchema", -): string[] => { - if (Array.isArray(value)) - return value.flatMap((child, index) => - missingPropertyDescriptions(child, `${path}[${index}]`), - ); - if (!isObject(value)) return []; - - const missing: string[] = []; - if (isObject(value.properties)) - for (const [property, schema] of Object.entries(value.properties)) { - const propertyPath = `${path}.properties.${property}`; - if (!isObject(schema) || typeof schema.description !== "string") - missing.push(propertyPath); - missing.push(...missingPropertyDescriptions(schema, propertyPath)); - } - for (const [key, child] of Object.entries(value)) - if ( - key !== "properties" && - key !== "examples" && - key !== "default" && - key !== "const" && - key !== "enum" - ) - missing.push(...missingPropertyDescriptions(child, `${path}.${key}`)); - return missing; -}; - -const isObject = (value: unknown): value is Readonly> => - typeof value === "object" && value !== null && !Array.isArray(value); - -const describesObject = (schema: unknown): boolean => { - if (!isObject(schema)) return false; - return ( - schema.type === "object" || - [schema.oneOf, schema.anyOf].some( - (variants) => - Array.isArray(variants) && - variants.length > 0 && - variants.every( - (variant: unknown) => isObject(variant) && variant.type === "object", - ), - ) - ); -}; diff --git a/tests/boundary/mcp/toolResult.test.ts b/tests/boundary/mcp/toolResult.test.ts index 224f47380..eb00c7874 100644 --- a/tests/boundary/mcp/toolResult.test.ts +++ b/tests/boundary/mcp/toolResult.test.ts @@ -1,14 +1,24 @@ import { describe, expect, it } from "vitest"; import { z } from "zod"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { McpServer } from "@modelcontextprotocol/server"; -import type { ToolContract } from "../../../src/contracts/toolContracts.js"; -import { ok } from "../../../src/domain/result.js"; -import { err } from "../../../src/domain/result.js"; +import { ProcessCaptureError } from "../../../src/process/capture/ProcessCaptureError.js"; +import { resolveProcessResult } from "../../../src/process/capture/ProcessCaptureLifecycle.js"; +import { + toolContract, + type ToolContract, +} from "../../../src/contracts/toolContracts.js"; +import { err, ok } from "../../../src/domain/result.js"; import { HopperProcessError } from "../../../src/domain/hopperErrors.js"; +import type { IncompleteProcessCaptureObservations } from "../../../src/domain/process/processCapture.js"; +import { processScenarioSchema } from "../../../src/domain/process/processScenario.js"; import { toCallToolResult } from "../../../src/server/toolResult.js"; import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; +import { emptyProcessCapture } from "../../../src/domain/process/processCapture.fixture.js"; import type { JsonValue } from "../../../src/domain/jsonValue.js"; import { evidenceResultOf } from "../../../src/contracts/toolOutputSchemas.js"; +import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js"; const contract: ToolContract = { name: "provider_neutral_fixture", @@ -36,6 +46,215 @@ const contract: ToolContract = { examples: [{ title: "Example fixture request", input: {} }], }; +describe("completed partial process capture MCP projection", () => { + it("preserves partial capture details over the MCP SDK transport", async () => { + const executionFailure = new Error("capture ended after a fixture error"); + const cleanup = { + owned_process_group: { + state: "unverified" as const, + reason: "process ownership token could not be read", + }, + terminal_renderer: { state: "cleaned" as const, reason: null }, + temporary_root: { state: "cleaned" as const, reason: null }, + }; + const capture = { + ...emptyProcessCapture(), + frames: [{ sequence: 0, at_ms: 0, data: "observed output" }], + event_journal: [], + }; + + let failure: ProcessCaptureError | undefined; + try { + resolveProcessResult(capture, executionFailure, cleanup); + } catch (cause: unknown) { + if (!(cause instanceof ProcessCaptureError)) throw cause; + failure = cause; + } + expect(failure).toBeDefined(); + if (failure === undefined) + throw new Error("expected cleanup-incomplete result"); + + const captureContract = toolContract("capture_process_scenario"); + const server = new McpServer({ + name: "process-partial-error", + version: "1", + }); + server.registerTool( + captureContract.name, + toolRegistrationOptions(captureContract), + async () => toCallToolResult(err(failure), captureContract), + ); + const client = new Client({ + name: "process-partial-error-client", + version: "1", + }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + try { + await server.connect(serverTransport); + await client.connect(clientTransport); + const result = await client.callTool({ + name: captureContract.name, + arguments: captureContract.examples[0]?.input ?? {}, + }); + + expect(result.isError).toBe(true); + expect(result.structuredContent).toMatchObject({ + error: { + code: "cleanup_incomplete", + details: { + cleanup_report: cleanup, + execution_failure: "capture ended after a fixture error", + partial_observation: { + capture: { + frames: [{ data: "observed output" }], + settlement: { cleanup_outcome: "failed" }, + }, + execution_failure: "capture ended after a fixture error", + }, + }, + }, + }); + const textContent = result.content.find((block) => block.type === "text"); + expect(textContent?.type === "text" ? textContent.text : undefined).toBe( + JSON.stringify(result.structuredContent), + ); + } finally { + await Promise.all([client.close(), server.close()]); + } + }); +}); + +describe("incomplete partial process observations MCP projection", () => { + it("preserves normalized raw observations over the MCP SDK transport", async () => { + const captureContract = toolContract("capture_process_scenario"); + const example = captureContract.examples[0]; + if (example === undefined) + throw new Error("missing process scenario example"); + const scenario = processScenarioSchema.parse({ + ...example.input, + normalization: { pids: false }, + }); + const rootPid = 45678; + const cleanup = { + owned_process_group: { + state: "unverified" as const, + reason: "process ownership token could not be read", + }, + terminal_renderer: { state: "cleaned" as const, reason: null }, + temporary_root: { state: "cleaned" as const, reason: null }, + }; + const observations: IncompleteProcessCaptureObservations = { + target_pid: { state: "available", value: rootPid }, + frames: { + state: "available", + value: [{ sequence: 0, at_ms: 0, data: "partial output" }], + }, + rendered_frames: { + state: "unavailable", + reason: "fixture renderer observation unavailable", + }, + interaction_events: { state: "available", value: [] }, + exit: { + state: "unavailable", + reason: "fixture terminal exit unavailable", + }, + settlement: { + state: "unavailable", + reason: "fixture process settlement unavailable", + }, + process_samples: { + state: "available", + value: [ + { + at_ms: 1, + pid: rootPid, + parent_pid: 1, + command: `fixture ${String(rootPid)}`, + process_group_id: rootPid, + session_id: rootPid, + }, + ], + }, + filesystem_snapshots: { + before: { state: "available", value: { files: [], truncated: false } }, + after: { + state: "unavailable", + reason: "fixture final snapshot unavailable", + }, + }, + event_journal: { state: "available", value: [] }, + manifest: { + state: "unavailable", + reason: "fixture capture manifest unavailable", + }, + }; + + let failure: ProcessCaptureError | undefined; + try { + resolveProcessResult( + undefined, + new Error("capture failed before completion"), + cleanup, + observations, + { scenario, rootPid }, + ); + } catch (cause: unknown) { + if (!(cause instanceof ProcessCaptureError)) throw cause; + failure = cause; + } + if (failure === undefined) + throw new Error("expected cleanup-incomplete result"); + + const server = new McpServer({ + name: "process-partial-observations", + version: "1", + }); + server.registerTool( + captureContract.name, + toolRegistrationOptions(captureContract), + async () => toCallToolResult(err(failure), captureContract), + ); + const client = new Client({ + name: "process-partial-observations-client", + version: "1", + }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + try { + await server.connect(serverTransport); + await client.connect(clientTransport); + const result = await client.callTool({ + name: captureContract.name, + arguments: example.input, + }); + + expect(result.isError).toBe(true); + expect(result.structuredContent).toMatchObject({ + error: { + details: { + partial_observation: { + observations: { + target_pid: { state: "available", value: rootPid }, + frames: { + state: "available", + value: [{ data: "partial output" }], + }, + process_samples: { + state: "available", + value: [{ pid: rootPid }], + }, + }, + }, + }, + }, + }); + } finally { + await Promise.all([client.close(), server.close()]); + } + }); +}); + describe("tool result projection", () => { it("exposes an actionable adapter code to MCP callers", () => { const result = toCallToolResult(err(new HopperProcessError(76)), contract); diff --git a/tests/boundary/mcp/toolSchemaValidity.test.ts b/tests/boundary/mcp/toolSchemaValidity.test.ts index e7ad269e0..708f8d40d 100644 --- a/tests/boundary/mcp/toolSchemaValidity.test.ts +++ b/tests/boundary/mcp/toolSchemaValidity.test.ts @@ -6,7 +6,7 @@ import { z } from "zod"; import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js"; import { emptyArraySchema } from "../../../src/domain/emptyArraySchema.js"; -import { processScenarioSchema } from "../../../src/domain/processScenario.js"; +import { processScenarioSchema } from "../../../src/domain/process/processScenario.js"; import { GENERATED_MCP_TOOL_CATALOG } from "../../../src/generatedMcpToolCatalog.js"; import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js"; @@ -14,6 +14,7 @@ interface ToolSchemas { readonly name: string; readonly inputSchema: Record; readonly outputSchema?: Record | undefined; + readonly annotations?: Record | undefined; } function schemaErrors(tools: readonly ToolSchemas[]): string[] { @@ -28,6 +29,155 @@ function schemaErrors(tools: readonly ToolSchemas[]): string[] { ); } +function expectStrictInputSchemaParity( + tools: readonly ToolSchemas[], + ajv: Ajv2020, +): void { + const advertised = new Map(tools.map((tool) => [tool.name, tool])); + const names = [ + "inspect_managed_artifact", + "inspect_managed_members", + "inspect_managed_native_boundaries", + "list_browser_targets", + "open_binary", + "close_binary", + "binary_session", + "find_changed_behavior", + "build_call_path", + "record_unknown", + "update_unknown", + ]; + for (const name of names) { + const contract = TOOL_CONTRACTS.find( + ({ name: toolName }) => toolName === name, + ); + const tool = advertised.get(name); + const example = contract?.examples[0]; + if (contract === undefined || tool === undefined || example === undefined) + throw new Error(`${name} did not have an advertised example`); + const malformed = { ...example.input, __unexpected_root_key__: true }; + expect(contract.inputSchema.safeParse(malformed).success, name).toBe(false); + expect(ajv.compile(tool.inputSchema)(malformed), name).toBe(false); + } +} + +function expectKnownAuthorityHints(tools: readonly ToolSchemas[]): void { + const advertised = new Map(tools.map((tool) => [tool.name, tool])); + const expected = { + read_bytes: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false, + }, + annotate_native_function: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + unset_bookmark: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: false, + }, + open_binary: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + close_binary: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: false, + }, + } as const; + + for (const [name, annotations] of Object.entries(expected)) + expect(advertised.get(name), name).toMatchObject({ annotations }); +} + +function expectRecursivePropertyDescriptions( + schema: unknown, + path: string, +): void { + if (!isRecord(schema)) return; + if (isRecord(schema.properties)) { + for (const [property, child] of Object.entries(schema.properties)) { + expect(child, `${path}.${property}`).toMatchObject({ + description: expect.any(String), + }); + expectRecursivePropertyDescriptions(child, `${path}.${property}`); + } + } + + for (const key of ["items", "additionalProperties"]) + if (schema[key] !== undefined) + expectRecursivePropertyDescriptions(schema[key], path); + for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) { + const children = schema[key]; + if (Array.isArray(children)) + children.forEach((child: unknown, index: number) => + expectRecursivePropertyDescriptions(child, `${path}.${key}[${index}]`), + ); + } +} + +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null && !Array.isArray(value); + +function resolveReference(schema: unknown, reference: string): unknown { + if (reference === "#") return schema; + if (!reference.startsWith("#/")) return undefined; + let value = schema; + for (const token of reference.slice(2).split("/")) { + const key = token.replaceAll("~1", "/").replaceAll("~0", "~"); + if ( + typeof value !== "object" || + value === null || + !Object.hasOwn(value, key) + ) + return undefined; + value = Reflect.get(value, key); + } + return value; +} + +function recursiveReferences(tools: readonly ToolSchemas[]): string[] { + return tools.flatMap((tool) => + (["inputSchema", "outputSchema"] as const).flatMap((kind) => { + const schema = tool[kind]; + const found = new Set(); + const visit = (node: unknown, active: readonly string[]): void => { + if (typeof node !== "object" || node === null) return; + for (const [key, child] of Object.entries(node)) { + if ( + [ + "$defs", + "definitions", + "examples", + "default", + "const", + "enum", + ].includes(key) + ) + continue; + if (key !== "$ref" || typeof child !== "string") visit(child, active); + else if (active.includes(child)) found.add(child); + else visit(resolveReference(schema, child), [...active, child]); + } + }; + visit(schema, []); + return [...found].map( + (reference) => `${tool.name}.${kind}: ${reference}`, + ); + }), + ); +} + const advertiseAndEnforceProcessEnvironmentKeyConstraint = async (): Promise => { const contract = TOOL_CONTRACTS.find( @@ -118,15 +268,6 @@ describe("MCP JSON Schema validity", () => { expect(validate(value)).toBe(false); }); - it("uses an oracle that rejects empty prefixItems under Draft 2020-12", () => { - const ajv = new Ajv2020({ strict: false, validateFormats: false }); - expect(ajv.defaultMeta()).toBe( - "https://json-schema.org/draft/2020-12/schema", - ); - expect(ajv.validateSchema({ type: "array", prefixItems: [] })).toBe(false); - expect(ajv.validateSchema({ type: "array", maxItems: 0 })).toBe(true); - }); - it("advertises valid input and output schemas for every canonical tool", async () => { const server = new McpServer({ name: "schema-validation", version: "0" }); const client = new Client({ name: "schema-validation", version: "0" }); @@ -148,6 +289,41 @@ describe("MCP JSON Schema validity", () => { TOOL_CONTRACTS.map(({ name }) => name).sort(), ); expect(schemaErrors(tools)).toEqual([]); + expect(recursiveReferences(tools)).toEqual([]); + expectKnownAuthorityHints(tools); + const byName = new Map(tools.map((tool) => [tool.name, tool])); + const catalogProjection = tools.map((tool) => ({ + name: tool.name, + title: tool.title, + description: tool.description, + inputSchema: tool.inputSchema, + outputSchema: tool.outputSchema, + annotations: tool.annotations, + })); + expect(catalogProjection).toEqual( + GENERATED_MCP_TOOL_CATALOG.map((tool) => ({ + name: tool.name, + title: tool.title, + description: tool.description, + inputSchema: tool.inputSchema, + outputSchema: tool.outputSchema, + annotations: tool.annotations, + })), + ); + for (const contract of TOOL_CONTRACTS) { + const tool = byName.get(contract.name); + expect(tool?.title?.trim(), contract.name).toBeTruthy(); + expect(tool?.description?.trim(), contract.name).toBeTruthy(); + expect(tool?.inputSchema.examples, contract.name).toEqual( + contract.examples.map(({ input }) => input), + ); + for (const example of contract.examples) + expect( + contract.inputSchema.safeParse(example.input).success, + `${contract.name}: ${example.title}`, + ).toBe(true); + expectRecursivePropertyDescriptions(tool?.inputSchema, contract.name); + } } finally { await Promise.allSettled([client.close(), server.close()]); } @@ -190,6 +366,8 @@ describe("MCP JSON Schema validity", () => { ).toBe(true); } + expectStrictInputSchemaParity(tools, ajv); + const nativeObservation = TOOL_CONTRACTS.find( ({ name }) => name === "observe_native_ui", ); @@ -220,13 +398,98 @@ describe("MCP JSON Schema validity", () => { await Promise.allSettled([client.close(), server.close()]); } }); - - it("ships valid input and output schemas in the generated catalog", () => { - expect(schemaErrors(GENERATED_MCP_TOOL_CATALOG)).toEqual([]); - }); }); it( "advertises and enforces the process-owned environment key constraint", advertiseAndEnforceProcessEnvironmentKeyConstraint, ); + +describe("MCP process input JSON Schema", () => { + it("advertises process strings without weakening terminal input", async () => { + const server = new McpServer({ + name: "process-schema-validation", + version: "0", + }); + const client = new Client({ + name: "process-schema-validation", + version: "0", + }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + for (const contract of TOOL_CONTRACTS) + server.registerTool( + contract.name, + toolRegistrationOptions(contract), + async () => ({ content: [] }), + ); + try { + await server.connect(serverTransport); + await client.connect(clientTransport); + const processContract = TOOL_CONTRACTS.find( + ({ name }) => name === "capture_process_scenario", + ); + const processTool = (await client.listTools()).tools.find( + ({ name }) => name === "capture_process_scenario", + ); + if (processContract === undefined || processTool === undefined) + throw new Error("Process scenario tool was not advertised"); + const validate = new Ajv2020({ + strict: false, + validateFormats: false, + }).compile(processTool.inputSchema); + const base = { executable: "/usr/bin/true" }; + for (const input of [ + { ...base, executable: "/usr/bin/true\0" }, + { ...base, arguments: ["\0"] }, + { ...base, working_directory: "/tmp\0" }, + { ...base, environment: { KEY: "value\0" } }, + { ...base, filesystem_observation_paths: ["/tmp\0"] }, + ]) { + expect(processContract.inputSchema.safeParse(input).success).toBe( + false, + ); + expect(validate(input)).toBe(false); + } + const terminalInput = { + ...base, + events: [{ type: "input", at_ms: 0, data: "\0" }], + }; + expect(processContract.inputSchema.safeParse(terminalInput).success).toBe( + true, + ); + expect(validate(terminalInput)).toBe(true); + } finally { + await Promise.allSettled([client.close(), server.close()]); + } + }); +}); + +it("ships nonrecursive input and output schemas in the generated catalog", () => { + expect(schemaErrors(GENERATED_MCP_TOOL_CATALOG)).toEqual([]); + expect(recursiveReferences(GENERATED_MCP_TOOL_CATALOG)).toEqual([]); +}); + +it("distinguishes recursive references from shared definitions", () => { + const diamond = { + type: "object", + properties: { + left: { $ref: "#/$defs/shared" }, + right: { $ref: "#/$defs/shared" }, + }, + $defs: { shared: { type: "string" } }, + }; + expect( + recursiveReferences([{ name: "diamond", inputSchema: diamond }]), + ).toEqual([]); + expect( + recursiveReferences([{ name: "root", inputSchema: { $ref: "#" } }]), + ).toEqual(["root.inputSchema: #"]); + const escaped = { + $ref: "#/$defs/a~1b", + $defs: { "a/b": { $ref: "#/$defs/a~1b" } }, + }; + expect( + recursiveReferences([{ name: "escaped", inputSchema: escaped }]), + ).toEqual(["escaped.inputSchema: #/$defs/a~1b"]); +}); diff --git a/tests/boundary/mcp/webNetworkCaptureMcp.test.ts b/tests/boundary/mcp/webNetworkCaptureMcp.test.ts new file mode 100644 index 000000000..602479ea7 --- /dev/null +++ b/tests/boundary/mcp/webNetworkCaptureMcp.test.ts @@ -0,0 +1,91 @@ +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { Ajv2020 } from "ajv/dist/2020.js"; +import { expect, it, onTestFinished } from "vitest"; +import { WebNetworkCaptureService } from "../../../src/application/WebNetworkCaptureService.js"; +import { toolContract } from "../../../src/contracts/toolContracts.js"; +import { parseEvidence } from "../../../src/domain/evidence.js"; +import { ok } from "../../../src/domain/result.js"; +import { createServer } from "../../../src/server/createServer.js"; +import { createTestBinarySession } from "../../fixtures/binarySession.js"; + +it("publishes valid SDK schemas and retains historical inline Evidence with the supplied session", async () => { + const service = new WebNetworkCaptureService({ + inspect: () => + Promise.resolve( + ok({ + artifact: { path: "/capture.har", sha256: "a".repeat(64), bytes: 10 }, + format: "har", + decoder: { id: "test-capture-port", name: "test port", version: "1" }, + container: { + reported: { creator: "fixture" }, + numeric_literals: [], + redactions: [], + records_pointer: "/log/entries", + }, + total_records: 0, + records: [], + runtime_attribution: "unknown", + limitations: ["synthetic application port, no real decoder claim"], + }), + ), + }); + const session = createTestBinarySession(() => { + throw new Error("Binary provider must not start"); + }); + const server = createServer(session, session, { webNetworkCapture: service }); + const client = new Client({ + name: "historical-capture-contract", + version: "1", + }); + onTestFinished(async () => { + await client.close(); + await server.close(); + await session.close(); + }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await server.connect(serverTransport); + await client.connect(clientTransport); + const advertised = (await client.listTools()).tools.find( + (tool) => tool.name === "inspect_web_network_capture", + ); + if (advertised?.outputSchema === undefined) + throw new Error("Capture inspection must advertise both schemas"); + const ajv = new Ajv2020({ strict: false, validateFormats: false }); + const inputSchema: Record = advertised.inputSchema; + const outputSchema: Record = advertised.outputSchema; + expect(ajv.validateSchema(inputSchema)).toBe(true); + expect(ajv.validateSchema(outputSchema)).toBe(true); + const args = { capture_path: "/capture.har", format: "har" }; + expect(ajv.validate(inputSchema, args)).toBe(true); + const response = await client.callTool({ + name: "inspect_web_network_capture", + arguments: args, + }); + expect(response.isError).not.toBe(true); + expect( + ajv.validate(outputSchema, response.structuredContent), + JSON.stringify(ajv.errors), + ).toBe(true); + const parsed = toolContract("inspect_web_network_capture").outputSchema.parse( + response.structuredContent, + ); + const evidence = parseEvidence(parsed.evidence); + expect(parsed.result).toEqual(evidence.normalized_result); + expect(session.evidenceById(evidence.evidence_id)).toEqual(evidence); + const invalid = await client.callTool({ + name: "inspect_web_network_capture", + arguments: { ...args, capture_path: "relative.har" }, + }); + expect(invalid.isError).toBe(true); + const invalidSensitive = await client.callTool({ + name: "inspect_web_network_capture", + arguments: { ...args, sensitive_values: ["secret"], mysecret: true }, + }); + expect(invalidSensitive.isError).toBe(true); + expect(invalidSensitive.structuredContent).toMatchObject({ + error: { code: "invalid_request", details: { issues: [{ path: [] }] } }, + }); + expect(JSON.stringify(invalidSensitive)).not.toContain("secret"); + expect(ajv.validate(inputSchema, { ...args, mysecret: true })).toBe(false); +}); diff --git a/tests/boundary/mcp/wireInputSchemaLimits.test.ts b/tests/boundary/mcp/wireInputSchemaLimits.test.ts new file mode 100644 index 000000000..be8bdae1c --- /dev/null +++ b/tests/boundary/mcp/wireInputSchemaLimits.test.ts @@ -0,0 +1,240 @@ +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { McpServer } from "@modelcontextprotocol/server"; +import { Ajv2020 } from "ajv/dist/2020.js"; +import { describe, expect, it } from "vitest"; +import { z } from "zod"; + +import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js"; +import { browserCaptureComparisonInputSchema } from "../../../src/domain/browserCaptureComparison.js"; +import { webPageInspectionSchema } from "../../../src/domain/browserObservation.js"; +import { GENERATED_MCP_TOOL_CATALOG } from "../../../src/generatedMcpToolCatalog.js"; +import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js"; + +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null && !Array.isArray(value); + +function resolveLocalReference(root: unknown, reference: string): unknown { + if (reference === "#") return root; + if (!reference.startsWith("#/")) return undefined; + let current = root; + for (const part of reference.slice(2).split("/")) { + const key = part.replaceAll("~1", "/").replaceAll("~0", "~"); + if (!isRecord(current) || !Object.hasOwn(current, key)) return undefined; + current = current[key]; + } + return current; +} + +// Count object/array and composition levels, following references per path. +// Definitions, property maps, and literal example/default data are not levels. +function inputDepth( + schema: unknown, + root: unknown = schema, + activeReferences: readonly string[] = [], +): number { + if (!isRecord(schema)) return 0; + let referenceDepth = 0; + if (typeof schema.$ref === "string") { + if (activeReferences.includes(schema.$ref)) return Number.POSITIVE_INFINITY; + const target = resolveLocalReference(root, schema.$ref); + if (target === undefined) return Number.POSITIVE_INFINITY; + referenceDepth = inputDepth(target, root, [ + ...activeReferences, + schema.$ref, + ]); + } + const childDepths: number[] = []; + for (const key of ["properties", "patternProperties", "dependentSchemas"]) { + const group = schema[key]; + if (isRecord(group)) + for (const child of Object.values(group)) + childDepths.push(inputDepth(child, root, activeReferences)); + } + for (const key of [ + "items", + "additionalProperties", + "contains", + "not", + "if", + "then", + "else", + "propertyNames", + "unevaluatedProperties", + "unevaluatedItems", + ]) + childDepths.push(inputDepth(schema[key], root, activeReferences)); + for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) { + const children = schema[key]; + if (Array.isArray(children)) + for (const child of children) + childDepths.push(inputDepth(child, root, activeReferences)); + } + const types = Array.isArray(schema.type) ? schema.type : [schema.type]; + const container = types.includes("object") || types.includes("array") ? 1 : 0; + const composition = ["anyOf", "oneOf", "allOf"].some((key) => + Array.isArray(schema[key]), + ) + ? 1 + : 0; + return Math.max( + referenceDepth, + container + composition + Math.max(0, ...childDepths), + ); +} + +function inputDepthViolations( + tools: readonly { name: string; inputSchema: unknown }[], +) { + return tools.flatMap(({ name, inputSchema }) => { + const depth = inputDepth(inputSchema); + return depth > 10 ? [{ name, depth }] : []; + }); +} + +async function withCatalog( + verify: (client: Client, calls: ReadonlyMap) => Promise, +): Promise { + const server = new McpServer({ name: "wire-limits", version: "0" }); + const calls = new Map(); + for (const contract of TOOL_CONTRACTS) + server.registerTool( + contract.name, + toolRegistrationOptions(contract), + async () => { + calls.set(contract.name, (calls.get(contract.name) ?? 0) + 1); + return { content: [], isError: true }; + }, + ); + const client = new Client({ name: "wire-limits", version: "0" }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + try { + await server.connect(serverTransport); + await client.connect(clientTransport); + await verify(client, calls); + } finally { + await Promise.allSettled([client.close(), server.close()]); + } +} + +describe("complete catalog input compatibility profile (#919)", () => { + it("follows shared and escaped references without treating data as schemas", () => { + const diamond = { + type: "object", + properties: { + left: { $ref: "#/$defs/a~1b~0c" }, + right: { $ref: "#/$defs/a~1b~0c" }, + }, + $defs: { + "a/b~c": { type: "object", properties: { id: { type: "string" } } }, + unused: { $ref: "#/$defs/unused" }, + }, + examples: [{ $ref: "#" }], + default: { $ref: "#" }, + }; + expect(inputDepth(diamond)).toBe(2); + expect( + inputDepth({ type: "object", properties: { child: { $ref: "#" } } }), + ).toBe(Number.POSITIVE_INFINITY); + expect( + inputDepth({ + type: "object", + properties: { + values: { + anyOf: [ + { type: "array", items: { type: "object" } }, + { type: "null" }, + ], + }, + }, + }), + ).toBe(4); + }); + + it("keeps every live and generated input within ten structural levels", async () => { + await withCatalog(async (client) => { + const { tools } = await client.listTools(); + expect(tools.map(({ name }) => name).sort()).toEqual( + TOOL_CONTRACTS.map(({ name }) => name).sort(), + ); + expect(inputDepthViolations(tools)).toEqual([]); + expect(inputDepthViolations(GENERATED_MCP_TOOL_CATALOG)).toEqual([]); + }); + }); + + it("describes capture round trips without altering domain or scenario schemas", () => { + const contract = TOOL_CONTRACTS.find( + ({ name }) => name === "compare_web_captures", + ); + if (contract === undefined) + throw new Error("Missing capture comparison contract"); + const original = z.toJSONSchema(browserCaptureComparisonInputSchema, { + io: "input", + }); + const projected = z.toJSONSchema(contract.inputSchema, { io: "input" }); + expect(inputDepth(original)).toBeGreaterThan(10); + expect(inputDepth(projected)).toBeLessThanOrEqual(10); + expect(isRecord(original.properties)).toBe(true); + expect(isRecord(projected.properties)).toBe(true); + if (!isRecord(original.properties) || !isRecord(projected.properties)) + throw new Error("Missing comparison properties"); + for (const key of ["normalization"]) + expect(projected.properties[key], key).toEqual(original.properties[key]); + expect(z.toJSONSchema(webPageInspectionSchema).properties).toHaveProperty( + "network", + ); + for (const example of contract.examples) + expect(contract.inputSchema.parse(example.input)).toEqual( + browserCaptureComparisonInputSchema.parse(example.input), + ); + const example = contract.examples[0]; + if (example === undefined) throw new Error("Missing scenario example"); + for (const malformed of [ + { before_scenario: {}, after_scenario: {} }, + { + ...example.input, + before: { inspection: {} }, + after: { inspection: {} }, + }, + { ...example.input, __unexpected_root_key__: true }, + ]) { + const originalResult = + browserCaptureComparisonInputSchema.safeParse(malformed); + const projectedResult = contract.inputSchema.safeParse(malformed); + expect(originalResult.success).toBe(false); + expect(projectedResult.success).toBe(false); + if (!originalResult.success && !projectedResult.success) + expect(projectedResult.error.issues).toEqual( + originalResult.error.issues, + ); + } + }); + + it("rejects incomplete round-trip payloads before invoking the handler", async () => { + await withCatalog(async (client, calls) => { + const tool = (await client.listTools()).tools.find( + ({ name }) => name === "compare_web_captures", + ); + if (tool === undefined) + throw new Error("Missing advertised comparison tool"); + const incomplete = { + before: { inspection: {} }, + after: { inspection: {} }, + }; + const validate = new Ajv2020({ + strict: false, + validateFormats: false, + }).compile(tool.inputSchema); + expect(validate(incomplete)).toBe(true); + expect( + browserCaptureComparisonInputSchema.safeParse(incomplete).success, + ).toBe(false); + const result = await client.callTool({ + name: tool.name, + arguments: incomplete, + }); + expect(result.isError).toBe(true); + expect(calls.get(tool.name) ?? 0).toBe(0); + }); + }); +}); diff --git a/src/application/ProcessEvidence.host.test.ts b/tests/boundary/process/ProcessEvidence.host.test.ts similarity index 67% rename from src/application/ProcessEvidence.host.test.ts rename to tests/boundary/process/ProcessEvidence.host.test.ts index d24146362..27300d44e 100644 --- a/src/application/ProcessEvidence.host.test.ts +++ b/tests/boundary/process/ProcessEvidence.host.test.ts @@ -1,10 +1,10 @@ import { expect, it } from "vitest"; -import { emptyUnverifiedProcessCapture } from "../domain/processCapture.fixture.js"; -import { parseProcessCapture } from "../domain/processCapture.js"; -import { parseProcessScenario } from "../domain/processScenario.js"; -import { createProcessCaptureEvidence } from "./ProcessEvidence.js"; -import { observeSettlement } from "./ProcessCaptureLifecycle.js"; +import { emptyUnverifiedProcessCapture } from "../../../src/domain/process/processCapture.fixture.js"; +import { parseProcessCapture } from "../../../src/domain/process/processCapture.js"; +import { parseProcessScenario } from "../../../src/domain/process/processScenario.js"; +import { createProcessCaptureEvidence } from "../../../src/application/process/ProcessEvidence.js"; +import { observeSettlement } from "../../../src/process/capture/ProcessCaptureLifecycle.js"; it("stamps process Evidence from the capture manifest host", () => { const scenario = parseProcessScenario({ diff --git a/src/application/ProcessEvidence.test.ts b/tests/boundary/process/ProcessEvidence.test.ts similarity index 84% rename from src/application/ProcessEvidence.test.ts rename to tests/boundary/process/ProcessEvidence.test.ts index b286cdcf0..1c35de761 100644 --- a/src/application/ProcessEvidence.test.ts +++ b/tests/boundary/process/ProcessEvidence.test.ts @@ -1,18 +1,22 @@ import { expect, it } from "vitest"; -import { createProcessCaptureEvidence } from "./ProcessEvidence.js"; -import { createRunManifest } from "./ProcessCaptureLifecycle.js"; -import { emptyUnverifiedProcessCapture } from "../domain/processCapture.fixture.js"; +import { createProcessCaptureEvidence } from "../../../src/application/process/ProcessEvidence.js"; +import { + createRunManifest, + observeLaunchedExecutable, + observeSelectedExecutable, +} from "../../../src/process/capture/ProcessCaptureLifecycle.js"; +import { emptyUnverifiedProcessCapture } from "../../../src/domain/process/processCapture.fixture.js"; import { compareProcessCaptures, parseProcessCapture, -} from "../domain/processCapture.js"; +} from "../../../src/domain/process/processCapture.js"; import { digestProcessCommitment, parseProcessScenario, processComparisonContract, processScenarioCommitment, -} from "../domain/processScenario.js"; +} from "../../../src/domain/process/processScenario.js"; const captureEvidenceForSensitiveInput = (data: string) => { const scenario = parseProcessScenario({ @@ -61,8 +65,20 @@ const captureEvidenceForEnvironmentSecret = async (value: string) => { environment: { API_TOKEN: value }, events: [{ type: "input", at_ms: 0, data: value }], }); + const selectedExecutable = await observeSelectedExecutable( + scenario.executable, + ); + const executableIdentity = observeLaunchedExecutable( + scenario.executable, + selectedExecutable, + ); + const manifest = await createRunManifest( + scenario, + new Date(0), + new Date(1), + executableIdentity, + ); const base = emptyUnverifiedProcessCapture(); - const manifest = await createRunManifest(scenario, new Date(0), new Date(1)); const scenarioManifest = { ...manifest, normalization_sha256: digestProcessCommitment(scenario.normalization), diff --git a/tests/boundary/process/darwinProcessRunTokenReader.test.ts b/tests/boundary/process/darwinProcessRunTokenReader.test.ts new file mode 100644 index 000000000..0c41c0ffd --- /dev/null +++ b/tests/boundary/process/darwinProcessRunTokenReader.test.ts @@ -0,0 +1,724 @@ +import { execFile, spawn, type ChildProcess } from "node:child_process"; +import { + access, + chmod, + mkdtemp, + readFile, + rm, + symlink, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { _electron as electron } from "playwright-core"; +import { expect, it, vi } from "vitest"; + +import { + createSystemProcessOwnershipHost, + systemProcessOwnershipHost, +} from "../../../src/process/ProcessOwnershipObservation.js"; +import { createDarwinProcessRunTokenReader } from "../../../src/process/DarwinProcessRunTokenReader.js"; +import { execFileOutput } from "../../../src/process/ExecFileOutput.js"; +import { normalizeCaptureFailure } from "../../../src/process/capture/ProcessHarness.js"; +import { PlaywrightElectronActiveProvider } from "../../../src/browser/PlaywrightElectronActiveProvider.js"; +import { electronActiveObservationInputSchema } from "../../../src/domain/javascript/electronActiveObservation.js"; + +const execFileAsync = promisify(execFile); +const onDarwin = process.platform === "darwin"; + +const writeBlockingCompiler = async (directory: string) => { + const executable = join(directory, "fake-xcrun"); + const statePath = join(directory, "compile-state.json"); + const releasePath = join(directory, "release-compiler"); + const source = `#!/usr/bin/env node +const fs = require("node:fs"); +const path = require("node:path"); +const statePath = ${JSON.stringify(statePath)}; +const releasePath = ${JSON.stringify(releasePath)}; +const state = fs.existsSync(statePath) + ? JSON.parse(fs.readFileSync(statePath, "utf8")) + : { calls: 0 }; +state.calls += 1; +const moduleIndex = process.argv.indexOf("-module-cache-path"); +state.root = path.dirname(process.argv[moduleIndex + 1]); +const outputIndex = process.argv.indexOf("-o"); +state.output = process.argv[outputIndex + 1]; +state.releasePath = releasePath; +fs.writeFileSync(statePath, JSON.stringify(state)); +if (state.calls === 1) { + const timer = setInterval(() => { + if (!fs.existsSync(releasePath)) return; + clearInterval(timer); + fs.writeFileSync(state.output, "compiled"); + }, 10); +} else { + fs.writeFileSync(state.output, "compiled"); +} +`; + await writeFile(executable, source); + await chmod(executable, 0o755); + return { executable, statePath, releasePath }; +}; + +const writeBlockingIdentityCompiler = async (directory: string) => { + const executable = join(directory, "fake-identity-xcrun"); + const statePath = join(directory, "identity-helper-state.json"); + const helperSource = `#!/usr/bin/env node +const fs = require("node:fs"); +fs.writeFileSync(${JSON.stringify(statePath)}, JSON.stringify({ + root: __dirname, +})); +setInterval(() => {}, 1000); +`; + const compilerSource = `#!/usr/bin/env node +const fs = require("node:fs"); +const outputIndex = process.argv.indexOf("-o"); +const output = process.argv[outputIndex + 1]; +fs.writeFileSync(output, ${JSON.stringify(helperSource)}); +fs.chmodSync(output, 0o755); +`; + await writeFile(executable, compilerSource); + await chmod(executable, 0o755); + return { executable, statePath }; +}; + +const waitForCompileState = async (statePath: string) => { + const deadline = Date.now() + 5_000; + while (Date.now() < deadline) { + try { + const parsed: unknown = JSON.parse(await readFile(statePath, "utf8")); + if ( + typeof parsed === "object" && + parsed !== null && + "calls" in parsed && + typeof parsed.calls === "number" && + "root" in parsed && + typeof parsed.root === "string" && + "output" in parsed && + typeof parsed.output === "string" && + "releasePath" in parsed && + typeof parsed.releasePath === "string" + ) + return { + calls: parsed.calls, + root: parsed.root, + output: parsed.output, + releasePath: parsed.releasePath, + }; + } catch { + await new Promise((resolve) => setTimeout(resolve, 10)); + } + } + throw new Error("fake compiler did not start"); +}; + +const waitForIdentityHelper = async (statePath: string) => { + const deadline = Date.now() + 5_000; + while (Date.now() < deadline) { + try { + const parsed: unknown = JSON.parse(await readFile(statePath, "utf8")); + if ( + typeof parsed === "object" && + parsed !== null && + "root" in parsed && + typeof parsed.root === "string" + ) + return { root: parsed.root }; + } catch { + await new Promise((resolve) => setTimeout(resolve, 10)); + } + } + throw new Error("fake identity helper did not start"); +}; + +type ElectronCaptureResult = Awaited< + ReturnType +>; +type ElectronCaptureOutcome = + | { readonly state: "result"; readonly value: ElectronCaptureResult } + | { readonly state: "rejected"; readonly cause: unknown }; + +it("reports an actionable missing Swift compiler without installing it", async () => { + const reader = createDarwinProcessRunTokenReader({ + xcrun: join(tmpdir(), "rea-no-such-xcrun"), + }); + try { + await expect( + reader.read([ + { + pid: 1, + parentPid: 0, + processGroupId: 1, + state: "S", + command: "fixture", + }, + ]), + ).rejects.toThrow(/requires the Apple Swift compiler via xcrun/u); + } finally { + await reader.close(); + } +}); + +it.skipIf(process.platform === "win32")( + "rethrows compile cancellation, removes its temporary root, and permits retry", + async () => { + const directory = await mkdtemp( + join(tmpdir(), "rea-process-token-abort-test-"), + ); + const fakeCompiler = await writeBlockingCompiler(directory); + const reader = createDarwinProcessRunTokenReader({ + xcrun: fakeCompiler.executable, + }); + const controller = new AbortController(); + const compilation = reader.prepare(controller.signal).then( + () => ({ state: "fulfilled" as const }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ); + try { + const firstAttempt = await waitForCompileState(fakeCompiler.statePath); + controller.abort(); + const outcome = await compilation; + + expect(outcome.state).toBe("rejected"); + if (outcome.state !== "rejected") + throw new Error( + "aborted native reader compilation unexpectedly succeeded", + ); + expect(outcome.cause).toMatchObject({ name: "AbortError" }); + expect( + normalizeCaptureFailure(outcome.cause, controller.signal), + ).toMatchObject({ reason: "cancelled", userCategory: "cancelled" }); + await expect(access(firstAttempt.root)).rejects.toThrow(); + + await reader.prepare(); + const retry = await waitForCompileState(fakeCompiler.statePath); + expect(retry).toMatchObject({ calls: 2 }); + await expect(access(retry.output)).resolves.toBeUndefined(); + await reader.close(); + await expect(access(retry.root)).rejects.toThrow(); + } finally { + if (!controller.signal.aborted) controller.abort(); + await compilation; + await reader.close(); + await rm(directory, { recursive: true, force: true }); + } + }, +); + +it.skipIf(process.platform === "win32").each(["first", "second"] as const)( + "keeps shared compilation alive when the %s waiter cancels", + async (cancelledWaiter) => { + const directory = await mkdtemp( + join(tmpdir(), "rea-process-token-shared-compile-test-"), + ); + const fakeCompiler = await writeBlockingCompiler(directory); + const reader = createDarwinProcessRunTokenReader({ + xcrun: fakeCompiler.executable, + }); + const firstController = new AbortController(); + const secondController = new AbortController(); + const firstWaiter = reader.prepare(firstController.signal).then( + (value) => ({ state: "fulfilled" as const, value }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ); + const secondWaiterPromise = (async () => { + const state = await waitForCompileState(fakeCompiler.statePath); + const secondWaiter = reader.prepare(secondController.signal).then( + (value) => ({ state: "fulfilled" as const, value }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ); + const cancelled = + cancelledWaiter === "first" ? firstWaiter : secondWaiter; + const surviving = + cancelledWaiter === "first" ? secondWaiter : firstWaiter; + (cancelledWaiter === "first" + ? firstController + : secondController + ).abort(); + const firstOutcome = await cancelled; + expect(firstOutcome.state).toBe("rejected"); + if (firstOutcome.state !== "rejected") + throw new Error("cancelled compilation waiter unexpectedly succeeded"); + expect(firstOutcome.cause).toMatchObject({ name: "AbortError" }); + await expect(access(state.root)).resolves.toBeUndefined(); + await writeFile(fakeCompiler.releasePath, "release"); + return { state, outcome: await surviving }; + })(); + + try { + const { state, outcome } = await secondWaiterPromise; + expect(state.calls).toBe(1); + expect(outcome.state).toBe("fulfilled"); + if (outcome.state !== "fulfilled") throw outcome.cause; + expect(outcome.value).toBe(state.output); + await reader.close(); + await expect(access(state.root)).rejects.toThrow(); + } finally { + if (!firstController.signal.aborted) firstController.abort(); + if (!secondController.signal.aborted) secondController.abort(); + await Promise.allSettled([firstWaiter, secondWaiterPromise]); + await reader.close(); + await rm(directory, { recursive: true, force: true }); + } + }, +); + +it.skipIf(process.platform === "win32" || process.getuid?.() === 0)( + "retains a failed compilation cleanup for a later close retry", + async () => { + const directory = await mkdtemp( + join(tmpdir(), "rea-process-token-cleanup-retry-test-"), + ); + const fakeCompiler = await writeBlockingCompiler(directory); + const reader = createDarwinProcessRunTokenReader({ + xcrun: fakeCompiler.executable, + }); + const controller = new AbortController(); + const preparation = reader.prepare(controller.signal).then( + () => ({ state: "fulfilled" as const }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ); + let compilationRoot: string | undefined; + try { + const state = await waitForCompileState(fakeCompiler.statePath); + compilationRoot = state.root; + await writeFile( + join(state.root, "retained.txt"), + "cleanup must remain tracked", + ); + await chmod(state.root, 0o000); + controller.abort(); + expect((await preparation).state).toBe("rejected"); + await expect(reader.close()).rejects.toThrow(); + await chmod(state.root, 0o700); + await reader.close(); + await expect(access(state.root)).rejects.toThrow(); + } finally { + if (!controller.signal.aborted) controller.abort(); + await preparation; + if (compilationRoot !== undefined) + await chmod(compilationRoot, 0o700).catch(() => undefined); + await reader.close(); + await rm(directory, { recursive: true, force: true }); + } + }, +); + +it.skipIf(process.platform === "win32")( + "aborts and cleans an active compilation when the reader closes", + async () => { + const directory = await mkdtemp( + join(tmpdir(), "rea-process-token-close-compile-test-"), + ); + const fakeCompiler = await writeBlockingCompiler(directory); + const reader = createDarwinProcessRunTokenReader({ + xcrun: fakeCompiler.executable, + }); + const preparation = reader.prepare().then( + () => ({ state: "fulfilled" as const }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ); + try { + const state = await waitForCompileState(fakeCompiler.statePath); + await reader.close(); + const outcome = await preparation; + expect(outcome.state).toBe("rejected"); + if (outcome.state !== "rejected") + throw new Error("closed reader compilation unexpectedly succeeded"); + expect(outcome.cause).toMatchObject({ name: "AbortError" }); + await expect(access(state.root)).rejects.toThrow(); + await expect(reader.prepare()).rejects.toThrow(/reader is closed/u); + await expect(reader.close()).resolves.toBeUndefined(); + } finally { + await Promise.allSettled([preparation]); + await reader.close(); + await rm(directory, { recursive: true, force: true }); + } + }, +); + +it.skipIf(process.platform === "win32")( + "maps cancellation during native preparation before Electron path resolution", + async () => { + const directory = await mkdtemp( + join(tmpdir(), "rea-electron-token-abort-test-"), + ); + const fakeCompiler = await writeBlockingCompiler(directory); + const reader = createDarwinProcessRunTokenReader({ + xcrun: fakeCompiler.executable, + }); + const prepareSpy = vi + .spyOn(systemProcessOwnershipHost, "prepare") + .mockImplementation(async (signal) => { + await reader.prepare(signal); + }); + const launchSpy = vi.spyOn(electron, "launch"); + const controller = new AbortController(); + let captureOutcome: Promise | undefined; + try { + const input = electronActiveObservationInputSchema.parse({ + executable_path: "/missing/electron", + application_path: "/missing/application.js", + }); + captureOutcome = new PlaywrightElectronActiveProvider() + .capture(input, { signal: controller.signal }) + .then( + (value) => ({ state: "result" as const, value }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ); + const compileState = await waitForCompileState(fakeCompiler.statePath); + controller.abort(); + const outcome = await captureOutcome; + + expect(outcome.state).toBe("result"); + if (outcome.state !== "result") throw outcome.cause; + expect(outcome.value).toMatchObject({ + ok: false, + error: { reason: "cancelled", userCategory: "cancelled" }, + }); + expect(launchSpy).not.toHaveBeenCalled(); + await expect(access(compileState.root)).rejects.toThrow(); + } finally { + if (!controller.signal.aborted) controller.abort(); + if (captureOutcome !== undefined) await captureOutcome; + prepareSpy.mockRestore(); + launchSpy.mockRestore(); + await reader.close(); + await rm(directory, { recursive: true, force: true }); + } + }, +); + +it.skipIf(process.platform === "win32")( + "preserves cancellation through system-host baseline identity inspection", + async () => { + const directory = await mkdtemp( + join(tmpdir(), "rea-process-baseline-abort-test-"), + ); + const fakeCompiler = await writeBlockingIdentityCompiler(directory); + const host = createSystemProcessOwnershipHost("darwin", process.env, { + darwinXcrun: fakeCompiler.executable, + }); + const controller = new AbortController(); + let helperRoot: string | undefined; + const baseline = + host.captureBaseline?.(controller.signal).then( + () => ({ state: "fulfilled" as const }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ) ?? Promise.resolve({ state: "missing" as const }); + try { + if (host.captureBaseline === undefined) + throw new Error("Darwin host did not expose process baselines"); + const helper = await waitForIdentityHelper(fakeCompiler.statePath); + helperRoot = helper.root; + controller.abort(); + const outcome = await baseline; + + expect(outcome.state).toBe("rejected"); + if (outcome.state !== "rejected") + throw new Error("aborted identity inspection unexpectedly succeeded"); + expect(outcome.cause).toMatchObject({ name: "AbortError" }); + expect( + normalizeCaptureFailure(outcome.cause, controller.signal), + ).toMatchObject({ reason: "cancelled", userCategory: "cancelled" }); + await expect(access(helper.root)).resolves.toBeUndefined(); + } finally { + if (!controller.signal.aborted) controller.abort(); + await baseline; + await host.close?.(); + if (helperRoot !== undefined) + await expect(access(helperRoot)).rejects.toThrow(); + await rm(directory, { recursive: true, force: true }); + } + }, +); + +it.skipIf(process.platform === "win32")( + "passes Electron cancellation into capture-local baseline inspection", + async () => { + const directory = await mkdtemp( + join(tmpdir(), "rea-electron-baseline-abort-test-"), + ); + const fakeCompiler = await writeBlockingIdentityCompiler(directory); + const reader = createDarwinProcessRunTokenReader({ + xcrun: fakeCompiler.executable, + }); + const prepareSpy = vi + .spyOn(systemProcessOwnershipHost, "prepare") + .mockResolvedValue(); + const baselineSpy = vi + .spyOn(systemProcessOwnershipHost, "captureBaseline") + .mockImplementation(async (signal) => { + await reader.identities( + [ + { + pid: process.pid, + parentPid: process.ppid, + processGroupId: process.pid, + state: "S", + command: process.execPath, + }, + ], + signal, + ); + return []; + }); + const launchSpy = vi.spyOn(electron, "launch"); + const controller = new AbortController(); + let captureOutcome: Promise | undefined; + try { + const input = electronActiveObservationInputSchema.parse({ + executable_path: process.execPath, + application_path: fileURLToPath(import.meta.url), + application_root: process.cwd(), + }); + captureOutcome = new PlaywrightElectronActiveProvider() + .capture(input, { signal: controller.signal }) + .then( + (value) => ({ state: "result" as const, value }), + (cause: unknown) => ({ state: "rejected" as const, cause }), + ); + const helper = await waitForIdentityHelper(fakeCompiler.statePath); + controller.abort(); + const outcome = await captureOutcome; + + expect(outcome.state).toBe("result"); + if (outcome.state !== "result") throw outcome.cause; + expect(outcome.value).toMatchObject({ + ok: false, + error: { reason: "cancelled", userCategory: "cancelled" }, + }); + expect(launchSpy).not.toHaveBeenCalled(); + await expect(access(helper.root)).resolves.toBeUndefined(); + } finally { + if (!controller.signal.aborted) controller.abort(); + if (captureOutcome !== undefined) await captureOutcome; + prepareSpy.mockRestore(); + baselineSpy.mockRestore(); + launchSpy.mockRestore(); + await reader.close(); + await rm(directory, { recursive: true, force: true }); + } + }, +); + +it.skipIf(!onDarwin)( + "parses exact NUL-delimited token records and rejects truncated argv", + async () => { + const root = await mkdtemp( + join(tmpdir(), "rea-process-token-parser-test-"), + ); + const executable = join(root, "parser-test"); + const core = fileURLToPath( + new URL( + "../../../bridge/process/ProcessRunTokenReader.swift", + import.meta.url, + ), + ); + const fixture = fileURLToPath( + new URL( + "../../fixtures/processRunTokenParserProbe.swift", + import.meta.url, + ), + ); + try { + await execFileOutput( + "/usr/bin/xcrun", + [ + "swiftc", + "-module-cache-path", + join(root, "modules"), + core, + fixture, + "-o", + executable, + ], + { timeout: 60_000 }, + ); + const { stdout } = await execFileAsync(executable, [], { + encoding: "utf8", + timeout: 10_000, + }); + expect(JSON.parse(stdout)).toEqual({ + argvDecoyIgnored: true, + alternatePaddingRead: true, + appleVectorTokenFailsClosed: true, + clearedAppleVectorTokenFailsClosed: true, + callerPfzBeforeTokenRead: true, + emptyLaterArgumentRead: true, + emptyEnvironmentFailsClosed: true, + emptyArgv0Read: true, + emptyEnvironmentRecordsBeforeTokenFailClosed: true, + duplicateTokenFailsClosed: true, + finalAssignmentArgumentRead: true, + missingAppleBoundaryFailsClosed: true, + nonAsciiPathRead: true, + realTokenRead: true, + reservedArgumentIgnored: true, + truncatedArgvFailsClosed: true, + zeroArgumentsRead: true, + }); + } finally { + await rm(root, { recursive: true, force: true }); + } + }, +); + +it.skipIf(!onDarwin)( + "retries a growing process table after ENOMEM and preserves other sysctl errors", + async () => { + const root = await mkdtemp( + join(tmpdir(), "rea-process-snapshot-retry-test-"), + ); + const executable = join(root, "snapshot-probe"); + const core = fileURLToPath( + new URL( + "../../../bridge/process/ProcessRunTokenReader.swift", + import.meta.url, + ), + ); + const fixture = fileURLToPath( + new URL( + "../../fixtures/processRunTokenSnapshotProbe.swift", + import.meta.url, + ), + ); + try { + await execFileOutput( + "/usr/bin/xcrun", + [ + "swiftc", + "-module-cache-path", + join(root, "modules"), + core, + fixture, + "-o", + executable, + ], + { timeout: 60_000 }, + ); + const { stdout } = await execFileAsync(executable, [], { + encoding: "utf8", + timeout: 10_000, + }); + expect(JSON.parse(stdout)).toEqual({ + preservedOtherErrno: true, + retriedAfterFreshSize: true, + }); + } finally { + await rm(root, { recursive: true, force: true }); + } + }, +); + +it.skipIf(!onDarwin)( + "reads only the exact ownership key from live process environments", + async () => { + const host = createSystemProcessOwnershipHost("darwin"); + const decoy = await startNodeChild({ + DECOY: "words REA_PROCESS_RUN_ID=synthetic-decoy", + }); + const owned = await startNodeChild({ + REA_PROCESS_RUN_ID: "synthetic-owned", + }); + try { + const processes = await host.listProcesses(); + const entries = [decoy, owned].map(({ pid }) => { + const entry = processes.find((process) => process.pid === pid); + if (entry === undefined) + throw new Error("test child is absent from process table"); + return entry; + }); + const observations = await host.runTokens?.(entries); + const identities = await host.processIdentities?.(entries); + expect(observations?.get(decoy.pid)).toEqual({ + state: "readable", + runId: undefined, + }); + expect(observations?.get(owned.pid)).toEqual({ + state: "readable", + runId: "synthetic-owned", + }); + expect(identities?.get(decoy.pid)?.state).toBe("readable"); + expect(identities?.get(owned.pid)?.state).toBe("readable"); + } finally { + await stopNodeChild(decoy.child); + await stopNodeChild(owned.child); + await host.close?.(); + } + }, +); + +it.skipIf(!onDarwin)( + "reads owned tokens with empty later arguments across executable path padding", + async () => { + const root = await mkdtemp(join(tmpdir(), "rea-process-token-padding-")); + const host = createSystemProcessOwnershipHost("darwin"); + const children: Awaited>[] = []; + try { + const executables = [join(root, "n"), join(root, "node")]; + for (const executable of executables) + await symlink(process.execPath, executable); + for (let index = 0; index < executables.length; index += 1) { + const executable = executables[index]; + if (executable === undefined) + throw new Error("test executable is missing"); + children.push( + await startNodeChild( + { REA_PROCESS_RUN_ID: `synthetic-padding-${String(index)}` }, + executable, + ["-e", "setInterval(() => {}, 1_000)", "", "FOO=bar"], + index === 0 ? "" : undefined, + ), + ); + } + const processes = await host.listProcesses(); + const entries = children.map(({ pid }) => { + const entry = processes.find((process) => process.pid === pid); + if (entry === undefined) + throw new Error("owned test child is absent from process table"); + return entry; + }); + const observations = await host.runTokens?.(entries); + expect(children.map(({ pid }) => observations?.get(pid))).toEqual([ + { state: "readable", runId: "synthetic-padding-0" }, + { state: "readable", runId: "synthetic-padding-1" }, + ]); + } finally { + for (const { child } of children) await stopNodeChild(child); + await host.close?.(); + await rm(root, { recursive: true, force: true }); + } + }, +); + +const startNodeChild = async ( + environment: NodeJS.ProcessEnv, + command = process.execPath, + arguments_: readonly string[] = ["-e", "setInterval(() => {}, 1_000)"], + argv0?: string, +) => { + const child = spawn(command, [...arguments_], { + env: environment, + stdio: "ignore", + ...(argv0 === undefined ? {} : { argv0 }), + }); + if (child.pid === undefined) + throw new Error("test child did not receive a PID"); + await new Promise((resolve, reject) => { + child.once("spawn", resolve); + child.once("error", reject); + }); + return { child, pid: child.pid }; +}; + +const stopNodeChild = async (child: ChildProcess) => { + if (child.exitCode !== null || child.signalCode !== null) return; + const exited = new Promise((resolve) => + child.once("exit", () => resolve()), + ); + child.kill("SIGKILL"); + await exited; +}; diff --git a/tests/boundary/process/filesystemSnapshotFifoRace.test.ts b/tests/boundary/process/filesystemSnapshotFifoRace.test.ts new file mode 100644 index 000000000..5843158e3 --- /dev/null +++ b/tests/boundary/process/filesystemSnapshotFifoRace.test.ts @@ -0,0 +1,54 @@ +import { randomUUID } from "node:crypto"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { expect, it } from "vitest"; + +import { + ProviderProcessSupervisor, + spawnOwnedProviderProcess, +} from "../../../src/process/ProviderProcess.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +it.skipIf(process.platform === "win32")( + "does not block when an observed regular file is replaced by a FIFO", + async () => { + const directory = await createTestTempDirectory("rea-snapshot-fifo-"); + const probe = fileURLToPath( + new URL( + "../../fixtures/filesystemSnapshotFifoProbe.mjs", + import.meta.url, + ), + ); + const snapshotModule = fileURLToPath( + new URL( + "../../../dist/process/capture/FilesystemSnapshot.js", + import.meta.url, + ), + ); + const launch = await spawnOwnedProviderProcess({ + command: process.execPath, + arguments: [probe, snapshotModule, directory], + runId: `rea-snapshot-fifo-${randomUUID()}`, + cwd: tmpdir(), + hostEnvironment: process.env, + }); + const supervisor = new ProviderProcessSupervisor({ + ...launch, + ownsProcessLifetime: true, + }); + + try { + const closed = await supervisor.waitForOutputClose(2_000); + expect(closed).toBe(true); + expect(supervisor.snapshot().stdout.text.trim()).toBe("rejected"); + } finally { + const stopped = await supervisor.stop({ + terminationGraceMs: 100, + killGraceMs: 500, + }); + expect(stopped.status).not.toBe("incomplete"); + } + }, +); diff --git a/tests/boundary/process/ownedCommand.test.ts b/tests/boundary/process/ownedCommand.test.ts new file mode 100644 index 000000000..c40e5fd57 --- /dev/null +++ b/tests/boundary/process/ownedCommand.test.ts @@ -0,0 +1,186 @@ +import { randomUUID } from "node:crypto"; +import { setTimeout as delay } from "node:timers/promises"; +import { expect, it } from "vitest"; +import { + OwnedCommandFailure, + runOwnedCommand, +} from "../../../src/process/OwnedCommand.js"; +import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js"; +import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js"; +import { waitForProviderProcessReady } from "../../fixtures/providerProcess.js"; + +const command = (script: string) => ({ + command: process.execPath, + arguments: ["-e", script], + runId: `rea-owned-command-test-${randomUUID()}`, +}); + +it("includes ownership preparation in the command deadline before provider creation", async () => { + let prepared = false; + await expect( + runOwnedCommand( + command("process.exit(0)"), + { + timeoutMs: 20, + diagnosticBytes: 1024, + }, + { + launcher: async (input) => { + await delay(80, undefined, { signal: input.signal }); + prepared = true; + throw new Error( + "Slow preparation must be aborted before provider creation", + ); + }, + }, + ), + ).rejects.toMatchObject({ reason: "timeout", snapshot: null }); + expect(prepared).toBe(false); +}); + +it("passes cancellation into native ownership preparation before provider creation", async () => { + const controller = new AbortController(); + await expect( + runOwnedCommand( + command("process.exit(0)"), + { timeoutMs: 2000, diagnosticBytes: 1024 }, + { + signal: controller.signal, + launcher: async (input) => { + expect(input.signal?.aborted).toBe(false); + controller.abort(); + input.signal?.throwIfAborted(); + throw new Error("Cancelled preparation must not launch a provider"); + }, + }, + ), + ).rejects.toMatchObject({ reason: "cancelled", snapshot: null }); +}); + +it("bounds retained diagnostics across both streams during a real output burst", async () => { + try { + await runOwnedCommand( + command( + 'process.stdout.write("a".repeat(256 * 1024)); process.stderr.write("b".repeat(256 * 1024))', + ), + { timeoutMs: 2_000, diagnosticBytes: 1024 }, + ); + throw new Error("An oversized producer must fail"); + } catch (cause: unknown) { + if (!(cause instanceof OwnedCommandFailure)) throw cause; + expect(cause.reason).toBe("output-limit"); + expect(cause.snapshot).not.toBeNull(); + expect( + (cause.snapshot?.stdout.bytes ?? 0) + (cause.snapshot?.stderr.bytes ?? 0), + ).toBeLessThanOrEqual(1024); + } +}); + +it("collects a real owned command and verifies its exit before returning diagnostics", async () => { + const result = await runOwnedCommand( + command('process.stdout.write("observed-output")'), + { timeoutMs: 2_000, diagnosticBytes: 1024 }, + ); + expect(result).toMatchObject({ + exitCode: 0, + signal: null, + stdout: { text: "observed-output", bytes: 15 }, + }); +}); + +it("cancels a real acquired process and independently releases it", async () => { + const controller = new AbortController(); + let pid: number | undefined; + const result = runOwnedCommand( + command( + 'process.stdout.write("ready\\n"); setInterval(() => undefined, 1000)', + ), + { timeoutMs: 5_000, diagnosticBytes: 1024 }, + { + signal: controller.signal, + launcher: async (input) => { + const process = await spawnOwnedProviderProcess(input); + pid = process.process.pid; + await waitForProviderProcessReady(process.process); + setImmediate(() => controller.abort()); + return process; + }, + }, + ); + await expect(result).rejects.toMatchObject({ + reason: "cancelled", + cleanupFailure: null, + }); + if (pid === undefined) throw new Error("Process was not acquired"); + const acquiredPid = pid; + expect(() => process.kill(acquiredPid, 0)).toThrow(); +}); + +it("returns a timeout for a running process after independent cleanup", async () => { + await expect( + runOwnedCommand(command("setInterval(() => undefined, 1000)"), { + timeoutMs: 50, + diagnosticBytes: 1024, + }), + ).rejects.toMatchObject({ reason: "timeout", cleanupFailure: null }); +}); + +it("honors cancellation during cleanup after a successful command", async () => { + const controller = new AbortController(); + await expect( + runOwnedCommand( + command("process.exit(0)"), + { timeoutMs: 2_000, diagnosticBytes: 1024 }, + { + signal: controller.signal, + launcher: async (input) => { + const launched = await spawnOwnedProviderProcess(input); + return { + ...launched, + cleanup: async () => { + const result = await cleanupOwnedProcessGroup(launched.ownership); + controller.abort(); + return result; + }, + }; + }, + }, + ), + ).rejects.toMatchObject({ reason: "cancelled", cleanupFailure: null }); +}); + +it("rejects oversized diagnostics even when the child exits immediately", async () => { + await expect( + runOwnedCommand(command('process.stdout.write("x".repeat(2048))'), { + timeoutMs: 2_000, + diagnosticBytes: 1024, + }), + ).rejects.toMatchObject({ reason: "output-limit", cleanupFailure: null }); +}); + +it("preserves original command failure together with failed cleanup resource identities", async () => { + const request = command("process.exit(2)"); + await expect( + runOwnedCommand( + request, + { timeoutMs: 2_000, diagnosticBytes: 1024 }, + { + launcher: async (input) => { + const process = await spawnOwnedProviderProcess(input); + return { + ...process, + cleanup: async () => ({ + cleaned: false as const, + reason: "synthetic cleanup could not be confirmed", + }), + }; + }, + }, + ), + ).rejects.toMatchObject({ + reason: "process", + cleanupFailure: "synthetic cleanup could not be confirmed", + resources: expect.arrayContaining([request.runId]), + snapshot: { exitCode: 2 }, + }); +}); diff --git a/tests/boundary/process/processCaptureCapability.test.ts b/tests/boundary/process/processCaptureCapability.test.ts index 5a8e31150..23d0219c9 100644 --- a/tests/boundary/process/processCaptureCapability.test.ts +++ b/tests/boundary/process/processCaptureCapability.test.ts @@ -4,7 +4,7 @@ import { processCaptureProbeFailureReason, processCaptureOwnershipUnavailableReason, probeProcessCaptureCapability, -} from "../../../src/application/ProcessCaptureCapability.js"; +} from "../../../src/process/capture/ProcessCaptureCapability.js"; describe("process capture capability diagnostics", () => { it("distinguishes missing native modules from runtime ABI mismatches", () => { @@ -52,6 +52,31 @@ describe("process capture capability diagnostics", () => { ).toContain("spawn EACCES"); }); + it("prepares Darwin ownership inspection before probing or admitting a PTY", async () => { + let loaded = false; + const capability = await probeProcessCaptureCapability({ + platform: "darwin", + prepareOwnershipInspector: async () => { + throw new Error( + "macOS process ownership inspection requires the Apple Swift compiler via xcrun", + ); + }, + loadPty: async () => { + loaded = true; + return await import("@lydell/node-pty"); + }, + }); + + expect(loaded).toBe(false); + expect(capability).toEqual({ + available: false, + backend: "node-pty", + reason: expect.stringContaining( + "requires the Apple Swift compiler via xcrun", + ), + }); + }); + it("keeps the ownership limitation separate from native PTY support", () => { expect(processCaptureOwnershipUnavailableReason("win32")).toContain( "Windows PTY process capture", diff --git a/tests/boundary/process/processCaptureCapability.ts b/tests/boundary/process/processCaptureCapability.ts index 4b345d734..3773ff88f 100644 --- a/tests/boundary/process/processCaptureCapability.ts +++ b/tests/boundary/process/processCaptureCapability.ts @@ -1,6 +1,6 @@ import { it } from "vitest"; -import { probeProcessCaptureCapability } from "../../../src/application/ProcessCaptureCapability.js"; +import { probeProcessCaptureCapability } from "../../../src/process/capture/ProcessCaptureCapability.js"; /** * Process-capture tests need real native PTY authority. Probing once at module diff --git a/tests/boundary/process/processCaptureLifecycle.test.ts b/tests/boundary/process/processCaptureLifecycle.test.ts index 6e16c4524..53e42aaa6 100644 --- a/tests/boundary/process/processCaptureLifecycle.test.ts +++ b/tests/boundary/process/processCaptureLifecycle.test.ts @@ -1,5 +1,6 @@ +import { createHash } from "node:crypto"; import { execFile } from "node:child_process"; -import { rm, symlink } from "node:fs/promises"; +import { access, readFile, rm, symlink, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -7,14 +8,378 @@ import { expect } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { itWithCaptureCapability } from "./processCaptureCapability.js"; -import { captureProcessScenario } from "../../../src/application/ProcessHarness.js"; -import { parseProcessScenario } from "../../../src/domain/processCapture.js"; +import { captureProcessScenario } from "../../../src/process/capture/ProcessHarness.js"; +import { snapshotRoots } from "../../../src/process/capture/FilesystemSnapshot.js"; +import { ProcessCaptureError } from "../../../src/process/capture/ProcessCaptureError.js"; +import { + type ProcessCaptureCleanupHost, + observeLaunchedExecutable, + observeSelectedExecutable, +} from "../../../src/process/capture/ProcessCaptureLifecycle.js"; +import { + parseProcessScenario, + type ProcessCapture, +} from "../../../src/domain/process/processCapture.js"; const processFixture = fileURLToPath( new URL("../../fixtures/processFidelity.mjs", import.meta.url), ); +const snapshotCancellationFixture = fileURLToPath( + new URL("../../fixtures/processSnapshotCancellation.mjs", import.meta.url), +); const execFileAsync = promisify(execFile); +type CaptureRun = Awaited>; +type PartialCapture = Extract< + NonNullable, + { readonly capture: unknown } +>["capture"]; +const captureObservations = ( + result: CaptureRun, +): { + readonly capture: ProcessCapture | PartialCapture; + readonly cleanupIncomplete: boolean; +} => { + if (result.ok) return { capture: result.value, cleanupIncomplete: false }; + if (!(result.error instanceof ProcessCaptureError)) throw result.error; + const report = result.error.cleanupReport; + const partial = result.error.partialObservation; + expect(result.error.reason).toBe("cleanup_incomplete"); + expect(report?.owned_process_group.state).toBe("unverified"); + const [summary, breakdown] = (report?.owned_process_group.reason ?? "").split( + ": ", + ); + expect(summary).toMatch( + /^process ownership token could not be read for [1-9][0-9]* live process\(es\)$/u, + ); + const categories = breakdown?.split(", ") ?? []; + expect(categories.length).toBeGreaterThan(0); + const expectedCategory = + process.platform === "linux" + ? /^environment_errno_(?:EACCES|EPERM)=[1-9][0-9]*$/u + : /^environment_unavailable=[1-9][0-9]*$/u; + for (const category of categories) expect(category).toMatch(expectedCategory); + expect(report?.terminal_renderer.state).toBe("cleaned"); + expect(report?.temporary_root.state).toBe("cleaned"); + if (partial === undefined || !("capture" in partial)) + throw new Error( + "cleanup-incomplete capture omitted completed observations", + ); + const capture = partial.capture; + expect(capture.settlement.cleanup_outcome).toBe("failed"); + return { capture, cleanupIncomplete: true }; +}; + +const processState = async (pid: number): Promise => { + try { + const { stdout } = await execFileAsync("ps", [ + "-o", + "stat=", + "-p", + String(pid), + ]); + return stdout.trim() || undefined; + } catch (cause: unknown) { + if ( + cause instanceof Error && + "code" in cause && + cause.code === 1 && + "stdout" in cause && + cause.stdout === "" + ) + return undefined; + throw cause; + } +}; + +const waitForProcessExit = async (pid: number): Promise => { + const deadline = Date.now() + 2_000; + while (Date.now() < deadline) { + const state = await processState(pid); + if (state === undefined || state.startsWith("Z")) return; + await new Promise((resolve) => setTimeout(resolve, 20)); + } + throw new Error(`Process ${String(pid)} did not exit within the test bound`); +}; + +const readDetachedFixturePids = async (marker: string): Promise => { + let contents: string; + try { + contents = await readFile(marker, "utf8"); + } catch (cause: unknown) { + if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") + return []; + throw cause; + } + const parsed: unknown = JSON.parse(contents); + if (typeof parsed !== "object" || parsed === null) return []; + const candidatePids = [ + "ownedPid" in parsed ? parsed.ownedPid : undefined, + "unownedPid" in parsed ? parsed.unownedPid : undefined, + ]; + return candidatePids.flatMap((pid) => + typeof pid === "number" && Number.isSafeInteger(pid) && pid > 1 + ? [pid] + : [], + ); +}; + +const terminateFixtureProcess = async (pid: number): Promise => { + try { + process.kill(pid, "SIGKILL"); + } catch (cause: unknown) { + if (!(cause instanceof Error && "code" in cause && cause.code === "ESRCH")) + throw cause; + } + await waitForProcessExit(pid); +}; + +itWithCaptureCapability( + "cleans detached run-token children omitted by sampling and preserves an unowned neighbor", + async () => { + const root = await createTestTempDirectory("rea-detached-cleanup-"); + const pidMarker = join(root, "children.json"); + let ownedPid: number | undefined; + let unownedPid: number | undefined; + try { + const result = await captureProcessScenario( + parseProcessScenario({ + executable: process.execPath, + arguments: [processFixture, "detached-cleanup", pidMarker], + working_directory: root, + settle_ms: 0, + timeout_ms: 5_000, + idle_timeout_ms: 5_000, + limits: { processes: 1 }, + }), + ); + const { capture, cleanupIncomplete } = captureObservations(result); + const parsed: unknown = JSON.parse(await readFile(pidMarker, "utf8")); + if ( + typeof parsed !== "object" || + parsed === null || + !("ownedPid" in parsed) || + typeof parsed.ownedPid !== "number" || + !("unownedPid" in parsed) || + typeof parsed.unownedPid !== "number" + ) + throw new TypeError("detached-child fixture wrote invalid PIDs"); + // Register fixture children before assertions so teardown can clean them + // even when a later expectation fails. + ownedPid = parsed.ownedPid; + unownedPid = parsed.unownedPid; + expect(capture.process_samples.some(({ pid }) => pid === ownedPid)).toBe( + false, + ); + if (cleanupIncomplete) + expect(capture.settlement.cleanup_outcome).toBe("failed"); + else + expect(result.ok && result.value.cleanup.owned_process_group).toBe( + "verified", + ); + await waitForProcessExit(ownedPid); + const unownedState = await processState(unownedPid); + expect(unownedState).toBeDefined(); + expect(unownedState?.startsWith("Z")).toBe(false); + } finally { + try { + let fixturePids: number[] = []; + let teardownFailure: unknown; + try { + fixturePids = await readDetachedFixturePids(pidMarker); + } catch (cause: unknown) { + teardownFailure = cause; + } + for (const pid of new Set([ + ...fixturePids, + ...(ownedPid === undefined ? [] : [ownedPid]), + ...(unownedPid === undefined ? [] : [unownedPid]), + ])) { + try { + await terminateFixtureProcess(pid); + } catch (cause: unknown) { + teardownFailure ??= cause; + } + } + if (teardownFailure !== undefined) throw teardownFailure; + } finally { + await rm(root, { recursive: true, force: true }); + } + } + }, + 10_000, +); + +itWithCaptureCapability( + "retains collected observations when completion and cleanup both fail", + async () => { + let snapshotCalls = 0; + const captureSnapshot: typeof snapshotRoots = async () => { + snapshotCalls += 1; + if (snapshotCalls === 1) return { files: [], truncated: false }; + throw new Error("fixture final snapshot failure"); + }; + const cleanupHost: ProcessCaptureCleanupHost = { + platform: process.platform, + cleanupProcessGroup: async () => ({ + cleaned: false, + reason: "fixture cleanup could not be verified", + }), + verifyTokenOwnedProcesses: async () => ({ + cleaned: false, + reason: "fixture cleanup could not be verified", + }), + removeTemporaryRoot: async (path) => + rm(path, { recursive: true, force: true }), + }; + const result = await captureProcessScenario( + parseProcessScenario({ + executable: process.execPath, + arguments: [processFixture, "partial"], + working_directory: dirname(processFixture), + settle_ms: 0, + timeout_ms: 5_000, + idle_timeout_ms: 5_000, + }), + undefined, + process.platform, + process.env, + captureSnapshot, + cleanupHost, + ); + + expect(result.ok).toBe(false); + if (result.ok) throw new Error("expected cleanup-incomplete failure"); + if (!(result.error instanceof ProcessCaptureError)) throw result.error; + const partial = result.error.partialObservation; + expect(result.error.reason).toBe("cleanup_incomplete"); + expect(result.error.executionFailure).toBe( + "fixture final snapshot failure", + ); + expect(partial).toBeDefined(); + if (partial === undefined || !("observations" in partial)) + throw new Error("expected incomplete process observations"); + const observations = partial.observations; + expect(observations.frames.state).toBe("available"); + if (observations.frames.state !== "available") + throw new Error("expected terminal output observations"); + expect( + observations.frames.value.map(({ data }) => data).join(""), + ).toContain("partial-frame"); + expect(observations.rendered_frames.state).toBe("available"); + if (observations.rendered_frames.state !== "available") + throw new Error("expected rendered terminal observations"); + expect(observations.rendered_frames.value.length).toBeGreaterThan(0); + expect(observations.interaction_events.state).toBe("available"); + expect(observations.exit).toMatchObject({ + state: "available", + value: { reason: "exited" }, + }); + expect(observations.settlement.state).toBe("available"); + expect(observations.process_samples.state).toBe("available"); + expect(observations.target_pid).toEqual({ state: "available", value: 1 }); + expect(observations.filesystem_snapshots.before.state).toBe("available"); + if (observations.filesystem_snapshots.before.state !== "available") + throw new Error("expected initial filesystem snapshot"); + expect(observations.filesystem_snapshots.before.value.truncated).toBe( + false, + ); + expect(observations.filesystem_snapshots.after).toEqual({ + state: "unavailable", + reason: + "Final filesystem snapshot failed: fixture final snapshot failure", + }); + expect(observations.event_journal.state).toBe("available"); + if (observations.event_journal.state !== "available") + throw new Error("expected capture event journal"); + expect( + observations.event_journal.value.some( + ({ collection }) => collection === "lifecycle", + ), + ).toBe(true); + expect(observations.manifest.state).toBe("unavailable"); + expect(snapshotCalls).toBe(2); + }, + 10_000, +); + +itWithCaptureCapability( + "keeps selected executable evidence separate when the path identity changes before validation", + async () => { + const root = await createTestTempDirectory("rea-executable-replaced-"); + const executable = join(root, "selected-executable"); + await symlink(process.execPath, executable); + try { + const selected = await observeSelectedExecutable(executable); + expect(selected.sha256).toMatch(/^[a-f0-9]{64}$/u); + await rm(executable); + await symlink("/bin/sh", executable); + expect(observeLaunchedExecutable(executable, selected)).toEqual({ + selectedSha256: selected.sha256, + sha256: null, + state: "unknown", + reason: "Executable path metadata changed between sampling and spawn.", + }); + } finally { + await rm(root, { recursive: true, force: true }); + } + }, +); + +itWithCaptureCapability( + "keeps the prelaunch executable sample when the selected path is replaced during capture", + async () => { + const root = await createTestTempDirectory("rea-executable-identity-"); + const executable = join(root, "selected-executable"); + const readyMarker = join(root, "child-ready"); + await symlink(process.execPath, executable); + const launchedDigest = createHash("sha256") + .update(await readFile(process.execPath)) + .digest("hex"); + try { + const capturePromise = captureProcessScenario( + parseProcessScenario({ + executable, + arguments: [processFixture, "ready-hang", readyMarker], + working_directory: root, + timeout_ms: 1_500, + idle_timeout_ms: 5_000, + }), + ); + const deadline = Date.now() + 2_000; + let ready = false; + while (!ready && Date.now() < deadline) { + try { + await access(readyMarker); + ready = true; + } catch { + await new Promise((resolve) => setTimeout(resolve, 10)); + } + } + expect(ready).toBe(true); + await rm(executable); + await symlink("/bin/sh", executable); + const result = await capturePromise; + const { capture } = captureObservations(result); + expect(capture.manifest).toMatchObject({ + selected_executable_sha256: launchedDigest, + executable_sha256: launchedDigest, + executable_identity: { + state: "path_metadata_unchanged", + reason: null, + }, + }); + expect(capture.exit.reason).toBe("timeout"); + expect(capture.limitations).toContain( + "The executable digest is a prelaunch file sample; matching path metadata immediately after spawn does not prove an atomic operating-system image binding.", + ); + } finally { + await rm(root, { recursive: true, force: true }); + } + }, + 10_000, +); + itWithCaptureCapability( "records external symlink metadata without following the target", async () => { @@ -28,16 +393,15 @@ itWithCaptureCapability( filesystem_observation_paths: [root], }), ); - expect(result.ok).toBe(true); - if (!result.ok) throw result.error; - const escaped = result.value.files_after.find((file) => + const { capture } = captureObservations(result); + const escaped = capture.files_after.find((file) => file.path.endsWith(":escape"), ); expect(escaped?.symlink_target).toBe("/etc/passwd"); - expect(result.value.truncated).toBe(false); - expect(JSON.stringify(result.value.files_after)).not.toContain(root); + expect(capture.truncated).toBe(false); + expect(JSON.stringify(capture.files_after)).not.toContain(root); expect( - result.value.files_after.some((file) => file.path.includes("passwd")), + capture.files_after.some((file) => file.path.includes("passwd")), ).toBe(false); } finally { await rm(root, { recursive: true, force: true }); @@ -57,13 +421,13 @@ itWithCaptureCapability( idle_timeout_ms: 5_000, }), ); - expect(timedOut.ok).toBe(true); - if (!timedOut.ok) throw timedOut.error; - expect(timedOut.value.exit.reason).toBe("timeout"); - expect(timedOut.value.cleanup).toEqual({ - owned_process_group: "verified", - temporary_root: "removed", - }); + const timedOutCapture = captureObservations(timedOut); + expect(timedOutCapture.capture.exit.reason).toBe("timeout"); + if (!timedOutCapture.cleanupIncomplete && timedOut.ok) + expect(timedOut.value.cleanup).toEqual({ + owned_process_group: "verified", + temporary_root: "removed", + }); const controller = new AbortController(); setTimeout(() => controller.abort(), 50); @@ -83,6 +447,114 @@ itWithCaptureCapability( }, ); +itWithCaptureCapability( + "classifies cancellation raised by the initial filesystem snapshot", + async () => { + const root = await createTestTempDirectory("rea-snapshot-initial-cancel-"); + const path = join(root, "input"); + await writeFile(path, "observed\n"); + const controller = new AbortController(); + const { signal } = controller; + const throwIfAborted = signal.throwIfAborted.bind(signal); + Object.defineProperty(signal, "throwIfAborted", { + value: () => { + controller.abort(); + throwIfAborted(); + }, + }); + + const result = await captureProcessScenario( + parseProcessScenario({ + executable: process.execPath, + working_directory: root, + filesystem_observation_paths: [path], + }), + signal, + ); + + expect(result.ok).toBe(false); + if (result.ok) throw new Error("expected initial snapshot cancellation"); + expect(result.error).toMatchObject({ + reason: "cancelled", + userCategory: "cancelled", + }); + }, +); + +itWithCaptureCapability( + "classifies cancellation raised by the final filesystem snapshot", + async () => { + const root = await createTestTempDirectory("rea-snapshot-final-cancel-"); + const controller = new AbortController(); + let initialSnapshotCompleted = false; + const captureSnapshot: typeof snapshotRoots = async (scenario, signal) => { + if (initialSnapshotCompleted) controller.abort(); + const snapshot = await snapshotRoots(scenario, signal); + initialSnapshotCompleted = true; + return snapshot; + }; + const resultPromise = captureProcessScenario( + parseProcessScenario({ + executable: process.execPath, + arguments: [snapshotCancellationFixture, root], + working_directory: dirname(snapshotCancellationFixture), + filesystem_observation_paths: [root], + settle_ms: 0, + }), + controller.signal, + process.platform, + process.env, + captureSnapshot, + ); + + const result = await resultPromise; + expect(result.ok).toBe(false); + if (result.ok) throw new Error("expected final snapshot cancellation"); + expect(initialSnapshotCompleted).toBe(true); + expect(result.error).toMatchObject({ + reason: "cancelled", + userCategory: "cancelled", + }); + }, +); + +itWithCaptureCapability( + "preserves filesystem errors that are not caller cancellation", + async () => { + const root = await createTestTempDirectory("rea-snapshot-io-error-"); + const obstruction = join(root, "not-a-directory"); + await writeFile(obstruction, "file"); + const controller = new AbortController(); + const captureSnapshot: typeof snapshotRoots = async (scenario, signal) => { + try { + return await snapshotRoots(scenario, signal); + } catch (cause: unknown) { + controller.abort(); + throw cause; + } + }; + + const result = await captureProcessScenario( + parseProcessScenario({ + executable: process.execPath, + working_directory: root, + filesystem_observation_paths: [join(obstruction, "child")], + }), + controller.signal, + process.platform, + process.env, + captureSnapshot, + ); + + expect(result.ok).toBe(false); + if (result.ok) throw new Error("expected filesystem observation failure"); + expect(result.error).toMatchObject({ + reason: "capture_failed", + cause: { code: "ENOTDIR" }, + }); + }, +); + itWithCaptureCapability( "captures input, Unicode, and resize across PTY startup", async () => { @@ -100,17 +572,16 @@ itWithCaptureCapability( idle_timeout_ms: 10_000, }), ); - if (!result.ok) throw result.error; - expect(result.ok).toBe(true); - const output = result.value.frames.map(({ data }) => data).join(""); + const { capture } = captureObservations(result); + const output = capture.frames.map(({ data }) => data).join(""); expect(output).toContain("prompt>"); expect(output).toContain("input:answer unicode:雪"); expect(output).toContain("resize:100x40"); - expect(result.value.interaction_events).toMatchObject([ + expect(capture.interaction_events).toMatchObject([ { type: "resize", outcome: "dispatched", scheduled_at_ms: 0 }, { type: "input", outcome: "dispatched", scheduled_at_ms: 0 }, ]); - const resized = result.value.rendered_frames.find( + const resized = capture.rendered_frames.find( ({ columns, rows, lines }) => columns === 100 && rows === 40 && @@ -118,7 +589,7 @@ itWithCaptureCapability( ); expect(resized).toBeDefined(); expect(resized?.lines.join("\n")).toContain("input:answer unicode:雪"); - expect(result.value.exit.code).toBe(0); + expect(capture.exit.code).toBe(0); }, ); @@ -135,12 +606,11 @@ itWithCaptureCapability( idle_timeout_ms: 2_000, }), ); - expect(result.ok).toBe(true); - if (!result.ok) throw result.error; - expect(result.value.interaction_events).toMatchObject([ + const { capture } = captureObservations(result); + expect(capture.interaction_events).toMatchObject([ { type: "signal", data: "SIGTERM", outcome: "dispatched" }, ]); - expect(result.value.exit).toMatchObject({ signal: 15, reason: "exited" }); + expect(capture.exit).toMatchObject({ signal: 15, reason: "exited" }); }, ); @@ -160,24 +630,22 @@ itWithCaptureCapability( idle_timeout_ms: 20_000, }), ); - expect(result.ok).toBe(true); - if (!result.ok) throw result.error; - expect(result.value.frames.map(({ data }) => data).join("")).toContain( + const { capture } = captureObservations(result); + expect(capture.frames.map(({ data }) => data).join("")).toContain( "input:answer", ); - expect(result.value.interaction_events).toMatchObject([ + expect(capture.interaction_events).toMatchObject([ { type: "resize", scheduled_at_ms: 25, outcome: "dispatched" }, { type: "input", scheduled_at_ms: 50, outcome: "dispatched" }, ]); expect( - result.value.interaction_events.every( + capture.interaction_events.every( ({ scheduled_at_ms, dispatched_at_ms }) => dispatched_at_ms >= scheduled_at_ms, ), ).toBe(true); expect( - result.value.frames.find(({ data }) => data.includes("input:answer")) - ?.at_ms, + capture.frames.find(({ data }) => data.includes("input:answer"))?.at_ms, ).toBeGreaterThanOrEqual(50); }, ); @@ -194,13 +662,12 @@ itWithCaptureCapability( idle_timeout_ms: 20_000, }), ); - expect(result.ok).toBe(true); - if (!result.ok) throw result.error; - expect(result.value.exit).toMatchObject({ code: 0, reason: "exited" }); - expect(result.value.frames.map(({ data }) => data).join("")).toContain( + const { capture } = captureObservations(result); + expect(capture.exit).toMatchObject({ code: 0, reason: "exited" }); + expect(capture.frames.map(({ data }) => data).join("")).toContain( "tree-ready", ); - const commands = result.value.process_samples.map(({ command }) => command); + const commands = capture.process_samples.map(({ command }) => command); expect(commands.some((command) => command.includes("tree-child"))).toBe( true, ); @@ -210,7 +677,7 @@ itWithCaptureCapability( expect( commands.some((command) => command.includes("tree-grandchild")), ).toBe(true); - expect(JSON.stringify(result.value.process_samples)).toContain( + expect(JSON.stringify(capture.process_samples)).toContain( dirname(processFixture), ); const { stdout } = await execFileAsync("ps", ["-axo", "command="]); diff --git a/tests/boundary/process/processCaptureNormalization.test.ts b/tests/boundary/process/processCaptureNormalization.test.ts index 4ff716b18..dbaef7c28 100644 --- a/tests/boundary/process/processCaptureNormalization.test.ts +++ b/tests/boundary/process/processCaptureNormalization.test.ts @@ -4,13 +4,13 @@ import { fileURLToPath } from "node:url"; import { expect, it } from "vitest"; import { z } from "zod"; -import { captureProcessScenario } from "../../../src/application/ProcessHarness.js"; -import { createProcessCaptureEvidence } from "../../../src/application/ProcessEvidence.js"; +import { captureProcessScenario } from "../../../src/process/capture/ProcessHarness.js"; +import { createProcessCaptureEvidence } from "../../../src/application/process/ProcessEvidence.js"; import { digestProcessCommitment, parseProcessCapture, parseProcessScenario, -} from "../../../src/domain/processCapture.js"; +} from "../../../src/domain/process/processCapture.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { itWithCaptureCapability } from "./processCaptureCapability.js"; @@ -41,7 +41,6 @@ itWithLinuxCaptureCapability.each([false, true])( idle_timeout_ms: 10_000, }); const result = await captureProcessScenario(scenario); - expect(result.ok).toBe(true); if (!result.ok) throw result.error; const capture = result.value; const identity = identitySchema.parse( diff --git a/tests/boundary/process/processCaptureTerminal.test.ts b/tests/boundary/process/processCaptureTerminal.test.ts index 028dd94fd..500d83c6b 100644 --- a/tests/boundary/process/processCaptureTerminal.test.ts +++ b/tests/boundary/process/processCaptureTerminal.test.ts @@ -6,8 +6,8 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { captureProcessScenario, probeProcessCaptureCapability, -} from "../../../src/application/ProcessHarness.js"; -import { parseProcessScenario } from "../../../src/domain/processCapture.js"; +} from "../../../src/process/capture/ProcessHarness.js"; +import { parseProcessScenario } from "../../../src/domain/process/processCapture.js"; it("captures one command's terminal output, selected files, and owned cleanup", async () => { const root = await createTestTempDirectory("rea-process-capture-"); diff --git a/tests/boundary/process/processHostIdentity.test.ts b/tests/boundary/process/processHostIdentity.test.ts index 816e78d04..995648ab5 100644 --- a/tests/boundary/process/processHostIdentity.test.ts +++ b/tests/boundary/process/processHostIdentity.test.ts @@ -1,10 +1,12 @@ import { expect, it } from "vitest"; -import { parseProcessScenario } from "../../../src/domain/processScenario.js"; +import { parseProcessScenario } from "../../../src/domain/process/processScenario.js"; import { createRunManifest, + observeLaunchedExecutable, + observeSelectedExecutable, observeSettlement, -} from "../../../src/application/ProcessCaptureLifecycle.js"; +} from "../../../src/process/capture/ProcessCaptureLifecycle.js"; it("uses one selected host identity for manifest and settlement", async () => { const scenario = parseProcessScenario({ @@ -16,10 +18,14 @@ it("uses one selected host identity for manifest and settlement", async () => { platform: "win32" as const, architecture: "arm64" as const, }; + const selectedExecutable = await observeSelectedExecutable( + scenario.executable, + ); const manifest = await createRunManifest( scenario, new Date(0), new Date(1), + observeLaunchedExecutable(scenario.executable, selectedExecutable), selectedHost, ); const settlement = await observeSettlement( diff --git a/tests/boundary/process/processPreparation.test.ts b/tests/boundary/process/processPreparation.test.ts new file mode 100644 index 000000000..262a0cb26 --- /dev/null +++ b/tests/boundary/process/processPreparation.test.ts @@ -0,0 +1,62 @@ +import { expect, it, vi } from "vitest"; + +import { snapshotRoots } from "../../../src/process/capture/FilesystemSnapshot.js"; +import { prepareProcessCapture } from "../../../src/process/capture/ProcessCaptureLifecycle.js"; +import { parseProcessScenario } from "../../../src/domain/process/processCapture.js"; + +it("prepares ownership inspection before snapshotting or allocating a target run", async () => { + const failure = new Error("native process identity inspection unavailable"); + const createTemporaryRoot = vi.fn(async () => "/unused"); + const cleanup = vi.fn(async () => undefined); + const prepareOwnershipInspector = vi.fn(async () => { + throw failure; + }); + const captureSnapshot: typeof snapshotRoots = async () => { + throw new Error("snapshot must not run after failed ownership preflight"); + }; + + await expect( + prepareProcessCapture( + parseProcessScenario({ + executable: "/bin/true", + working_directory: "/tmp", + }), + undefined, + captureSnapshot, + { prepareOwnershipInspector, createTemporaryRoot, cleanup }, + ), + ).rejects.toBe(failure); + + expect(prepareOwnershipInspector).toHaveBeenCalledOnce(); + expect(createTemporaryRoot).not.toHaveBeenCalled(); + expect(cleanup).not.toHaveBeenCalled(); +}); + +it("cleans the temporary root when cancellation arrives during identity capture", async () => { + const controller = new AbortController(); + const temporaryRoot = "/temporary-capture-root"; + const cleanup = vi.fn(async () => undefined); + const host = { + createTemporaryRoot: vi.fn(async () => temporaryRoot), + captureOwnershipBaseline: vi.fn(async (signal?: AbortSignal) => { + expect(signal).toBe(controller.signal); + controller.abort(); + return []; + }), + cleanup, + }; + + await expect( + prepareProcessCapture( + parseProcessScenario({ + executable: "/bin/true", + working_directory: "/tmp", + }), + controller.signal, + async () => ({ files: [], truncated: false }), + host, + ), + ).rejects.toThrow(/cancelled/u); + expect(host.captureOwnershipBaseline).toHaveBeenCalledWith(controller.signal); + expect(cleanup).toHaveBeenCalledWith(temporaryRoot); +}); diff --git a/tests/boundary/process/providerProcess.test.ts b/tests/boundary/process/providerProcess.test.ts index 24f817962..8e1440eff 100644 --- a/tests/boundary/process/providerProcess.test.ts +++ b/tests/boundary/process/providerProcess.test.ts @@ -8,7 +8,10 @@ import { PendingOperations } from "../../../src/process/PendingOperations.js"; import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js"; import { ProviderStartupDeadline } from "../../../src/process/ProviderDeadline.js"; import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js"; -import { observeOwnedProcessLineage } from "../../../src/process/ProcessOwnershipObservation.js"; +import { + observeOwnedProcessLineage, + systemProcessOwnershipHost, +} from "../../../src/process/ProcessOwnershipObservation.js"; import { ProviderProcessSupervisor, spawnOwnedProviderProcess, @@ -371,6 +374,52 @@ describe("provider process host injection", () => { }); }); +describe("provider startup cancellation", () => { + it("rejects an already cancelled launch before preparing or spawning", async () => { + const controller = new AbortController(); + const cancellation = new Error("provider launch already cancelled"); + controller.abort(cancellation); + + await expect( + spawnOwnedProviderProcess({ + command: `${tmpdir()}/rea-provider-must-not-spawn-after-cancel`, + arguments: [], + runId: "provider-process-pre-aborted-run", + signal: controller.signal, + }), + ).rejects.toBe(cancellation); + }); + + it.skipIf(process.platform !== "darwin")( + "forwards cancellation through native ownership preparation before spawning", + async () => { + const controller = new AbortController(); + const cancellation = new Error("cancelled during native preparation"); + const prepare = vi.spyOn(systemProcessOwnershipHost, "prepare"); + prepare.mockImplementation(async (signal) => { + expect(signal).toBe(controller.signal); + await new Promise((resolve) => { + signal?.addEventListener("abort", () => resolve(), { once: true }); + }); + }); + + try { + const starting = spawnOwnedProviderProcess({ + command: `${tmpdir()}/rea-provider-must-not-spawn-after-cancel`, + arguments: [], + runId: "provider-process-cancelled-preparation-run", + signal: controller.signal, + }); + controller.abort(cancellation); + await expect(starting).rejects.toBe(cancellation); + expect(prepare).toHaveBeenCalledOnce(); + } finally { + prepare.mockRestore(); + } + }, + ); +}); + describe("provider process spawning primitives", () => { it("spawns an owned process group with exact identity coordinates", async () => { const runId = "provider-process-run"; diff --git a/tests/boundary/process/providerProcessOutput.test.ts b/tests/boundary/process/providerProcessOutput.test.ts new file mode 100644 index 000000000..888394c99 --- /dev/null +++ b/tests/boundary/process/providerProcessOutput.test.ts @@ -0,0 +1,42 @@ +import { expect, it } from "vitest"; +import { + ProviderProcessSupervisor, + type ProviderProcessDiagnostic, +} from "../../../src/process/ProviderProcess.js"; +import { + spawnProviderProcessFixture, + stopProviderProcessFixture, +} from "../../fixtures/providerProcess.js"; + +it("bounds capture before diagnostic callbacks while draining both output streams", async () => { + const child = spawnProviderProcessFixture("burst", 70_000); + const diagnostics: ProviderProcessDiagnostic[] = []; + const supervisor = new ProviderProcessSupervisor( + { process: child, ownsProcessLifetime: true }, + { + maxDiagnosticBytes: 1024, + onDiagnostic: (event) => { + const snapshot = supervisor.snapshot(); + expect( + snapshot.stdout.bytes + snapshot.stderr.bytes, + ).toBeLessThanOrEqual(1024); + diagnostics.push(event); + }, + }, + ); + try { + expect(await supervisor.waitForOutputClose(2_000)).toBe(true); + expect( + diagnostics.some((event) => event.type === "output" && event.truncated), + ).toBe(true); + for (const stream of ["stdout", "stderr"]) + expect( + diagnostics + .filter((event) => event.type === "output" && event.stream === stream) + .at(-1), + ).toMatchObject({ totalBytes: 70_000 }); + } finally { + await supervisor.stop(); + await stopProviderProcessFixture(child); + } +}); diff --git a/tests/boundary/process/selectedExecutableCancellation.test.ts b/tests/boundary/process/selectedExecutableCancellation.test.ts new file mode 100644 index 000000000..238737a58 --- /dev/null +++ b/tests/boundary/process/selectedExecutableCancellation.test.ts @@ -0,0 +1,22 @@ +import { open, stat, type FileHandle } from "node:fs/promises"; +import { expect, it } from "vitest"; +import { observeSelectedExecutable } from "../../../src/process/capture/ProcessCaptureLifecycle.js"; + +it("cancels after real metadata IO without creating an already-aborted stream or leaking the file handle", async () => { + const controller = new AbortController(); + let opened: FileHandle | undefined; + await expect( + observeSelectedExecutable(process.execPath, controller.signal, { + async open(path, flags) { + opened = await open(path, flags); + return opened; + }, + async stat(path) { + const metadata = await stat(path, { bigint: true }); + controller.abort(); + return metadata; + }, + }), + ).rejects.toMatchObject({ reason: "cancelled" }); + expect(opened?.fd).toBe(-1); +}); diff --git a/tests/boundary/providers/ghidra/ghidraClient.test.ts b/tests/boundary/providers/ghidra/ghidraClient.test.ts index fa77d8c38..45dedebd0 100644 --- a/tests/boundary/providers/ghidra/ghidraClient.test.ts +++ b/tests/boundary/providers/ghidra/ghidraClient.test.ts @@ -36,6 +36,7 @@ const HOST_TRANSPORT: GhidraTransportKind = type FixtureMode = | "success" | "fragmented" + | "oversized_result" | "wrong_identity" | "malformed" | "contradictory" @@ -188,6 +189,32 @@ describe("GhidraClient", () => { 108, ); else expect(launcher.endpointPaths[0]).toMatch(/bridge-endpoint\.json$/u); + + await expect( + client.callTool("list_strings", { document: null }), + ).resolves.toMatchObject({ + ok: true, + value: [{ address: "0x2000", value: "fixture 雪🦊" }], + }); + }); + + it("accepts an owned-socket result above the former 64 MiB ceiling", async () => { + const client = clientFor(new FixtureLauncher("oversized_result")); + const result = await client.callTool("list_strings", { document: null }); + + expect(result.ok).toBe(true); + if (!result.ok) return; + if (!Array.isArray(result.value)) + throw new TypeError("Ghidra fixture result was not an inventory array"); + const first = result.value[0]; + if (typeof first !== "object" || first === null || Array.isArray(first)) + throw new TypeError("Ghidra fixture inventory item was not an object"); + const value = first.value; + if (typeof value !== "string") + throw new TypeError("Ghidra fixture string value was missing"); + expect(value.length).toBe(64 * 1024 * 1024 + 1); + expect(value.slice(0, 4)).toBe("xxxx"); + expect(value.slice(-4)).toBe("xxxx"); }); it("completes the same authenticated handshake over loopback TCP", async () => { diff --git a/tests/boundary/providers/hopper/hopperClient.protocol.test.ts b/tests/boundary/providers/hopper/hopperClient.protocol.test.ts index 70bce0078..e263c6861 100644 --- a/tests/boundary/providers/hopper/hopperClient.protocol.test.ts +++ b/tests/boundary/providers/hopper/hopperClient.protocol.test.ts @@ -79,6 +79,48 @@ describe("HopperClient protocol", () => { expect(results).toHaveLength(HOPPER_OPERATIONS.length); expect(results.every((result) => result.ok)).toBe(true); }); +}); + +describe("HopperClient response lifecycle", () => { + it("reassembles a fragmented Unicode reply from the owned socket", async () => { + const client = await startClient(); + + await expect( + client.callTool("echo", { label: "雪🦊", fragmented: true }), + ).resolves.toEqual({ + ok: true, + value: { label: "雪🦊", fragmented: true }, + }); + }); + + it("accepts an owned-socket response above the former 10 MiB ceiling", async () => { + const client = await startClient(); + const label = "x".repeat(10 * 1024 * 1024 + 1); + + const result = await client.callTool("echo", { label }); + + expect(result).toEqual({ ok: true, value: { label } }); + }); + + it("clears a partial reply when closing and restarting the owned session", async () => { + const launcher = new FixtureLauncher(); + const client = await startClient(launcher); + const partialReply = client.callTool("partial_then_wait", {}); + const request = await launcher.waitForRequest("partial_then_wait"); + const partialWrite = await launcher.waitForRequest("fixture_partial_write"); + expect(partialWrite.id).toBe(request.id); + + await client.close(); + await expect(partialReply).resolves.toMatchObject({ + ok: false, + error: { _tag: "HopperProcessError" }, + }); + await expect(client.start()).resolves.toMatchObject({ ok: true }); + await expect(client.callTool("echo", { label: "fresh" })).resolves.toEqual({ + ok: true, + value: { label: "fresh" }, + }); + }); it("rejects a bridge session with the wrong capability token", async () => { const client = new HopperClient({ diff --git a/tests/boundary/providers/hopper/linuxPrivateDisplay.test.ts b/tests/boundary/providers/hopper/linuxPrivateDisplay.test.ts index b4e378f82..9310d5463 100644 --- a/tests/boundary/providers/hopper/linuxPrivateDisplay.test.ts +++ b/tests/boundary/providers/hopper/linuxPrivateDisplay.test.ts @@ -25,6 +25,12 @@ const helperPath = fileURLToPath( const hangingHelperPath = fileURLToPath( new URL("../../../fixtures/x11ProbeHang.py", import.meta.url), ); +const dialogDetectionFixturePath = fileURLToPath( + new URL("../../../fixtures/hopperDialogDetection.py", import.meta.url), +); +const cleanupTimeoutFixturePath = fileURLToPath( + new URL("../../../fixtures/hopperCleanupTimeout.py", import.meta.url), +); type DiagnosticOverrides = Partial< Omit @@ -81,6 +87,46 @@ const failureExit = (code: HopperStartupFailureCode): number => ? 79 : 70; +describe("Hopper demo dialog detection", () => { + it.skipIf(process.platform !== "linux")( + "uses the Hopper transient-parent relation instead of exact dialog geometry", + () => { + const result = spawnSync( + "/usr/bin/python3", + [dialogDetectionFixturePath, helperPath], + { encoding: "utf8" }, + ); + expect(result.status).toBe(0); + expect(JSON.parse(result.stdout)).toEqual({ + dialog: [10, 20, 100, 1200, 284], + click: [136, 362], + wrong_parent_rejected: true, + }); + }, + ); +}); + +describe("Hopper process cleanup", () => { + it.skipIf(process.platform !== "linux")( + "kills a child that ignores SIGTERM after the bounded timeout", + () => { + const result = spawnSync( + "/usr/bin/python3", + [cleanupTimeoutFixturePath, helperPath], + { encoding: "utf8", timeout: 5_000 }, + ); + expect(result.status).toBe(0); + const parsed = JSON.parse(result.stdout) as { + readonly returncode: number; + readonly elapsed_ms: number; + }; + expect(parsed.returncode).toBe(-9); + expect(parsed.elapsed_ms).toBeGreaterThanOrEqual(1_800); + expect(parsed.elapsed_ms).toBeLessThan(4_000); + }, + ); +}); + describe("Linux private display selection", () => { it("prefers a successful direct Xvfb probe", async () => { const calls: LinuxPrivateDisplayRunnableStrategy[] = []; diff --git a/tests/boundary/providers/managed/managedArmMachine.test.ts b/tests/boundary/providers/managed/managedArmMachine.test.ts index 0436c631d..7680423b8 100644 --- a/tests/boundary/providers/managed/managedArmMachine.test.ts +++ b/tests/boundary/providers/managed/managedArmMachine.test.ts @@ -3,7 +3,6 @@ import { join } from "node:path"; import { expect, it } from "vitest"; import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js"; -import { parseMachineType } from "../../../../src/domain/peInspection.js"; import { buildManagedPeFixture } from "../../../../src/dotnet/ManagedPe.fixture.js"; import { ManagedStaticProvider } from "../../../../src/dotnet/ManagedStaticProvider.js"; @@ -24,7 +23,6 @@ it.each([0x01c0, 0x01c2, 0x01c4])( architecture: "arm", managed: true, }); - expect(parseMachineType(machine)).toBe("arm"); const client = new ManagedStaticProvider().createClient(parsed.value); try { for (const operation of [ diff --git a/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts b/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts index f58c5032b..c6e187acb 100644 --- a/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts +++ b/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts @@ -58,26 +58,6 @@ describe("artifact archive inventory", () => { expect(inspectionEvidence.evidence_links).toEqual( inspection.evidence_links, ); - const expandedInspection = artifactInspectionResultSchema.parse({ - ...inspection, - subject: { ...inspection.subject, root_format: "f".repeat(129) }, - observations: inspection.observations.map((observation) => ({ - ...observation, - subject: "s".repeat(4_097), - })), - hypotheses: inspection.hypotheses.map((hypothesis) => ({ - ...hypothesis, - statement: "s".repeat(4_097), - basis_evidence_ids: Array(9).fill(inspection.evidence_links[0] ?? ""), - })), - next_probes: inspection.next_probes.map((probe) => ({ - ...probe, - operation: "o".repeat(129), - })), - limitations: Array.from({ length: 1_001 }, (_, index) => `gap-${index}`), - }); - expect(expandedInspection.subject.root_format).toHaveLength(129); - expect(expandedInspection.limitations).toHaveLength(1_001); const cancellation = new AbortController(); cancellation.abort(); await expect( diff --git a/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts b/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts index 98ab35808..3a3e78d43 100644 --- a/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts +++ b/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts @@ -80,13 +80,6 @@ describe("artifact archive safety", () => { ); it("rejects unsafe paths and collisions", async () => { - expect( - normalizeArtifactPath( - Array.from({ length: 24 }, (_, index) => `level${String(index)}`).join( - "/", - ) + "/entry.js", - ).split("/"), - ).toHaveLength(25); let unsafePathError: unknown; try { normalizeArtifactPath("../escape"); diff --git a/tests/boundary/providers/native/artifactProvider.directoryErrors.test.ts b/tests/boundary/providers/native/artifactProvider.directoryErrors.test.ts new file mode 100644 index 000000000..25264cfa1 --- /dev/null +++ b/tests/boundary/providers/native/artifactProvider.directoryErrors.test.ts @@ -0,0 +1,57 @@ +import { chmod, mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js"; +import { ArtifactProvider } from "../../../../src/artifacts/ArtifactProvider.js"; +import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js"; +import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; + +describe("artifact directory diagnostics", () => { + it.skipIf(process.platform === "win32")( + "preserves the inaccessible member path and errno through provider projection", + async () => { + const root = await createTestTempDirectory("rea-directory-eacces-"); + const appPath = join(root, "Denied.app"); + const contentsPath = join(appPath, "Contents"); + const executablePath = join(contentsPath, "MacOS", "Fixture"); + const blockedPath = join(contentsPath, "blocked.txt"); + await mkdir(join(contentsPath, "MacOS"), { recursive: true }); + await writeFile( + join(contentsPath, "Info.plist"), + "CFBundleExecutableFixture", + ); + const machHeader = Buffer.alloc(8); + machHeader.writeUInt32BE(0xfeedfacf, 0); + machHeader.writeUInt32BE(0x0100000c, 4); + await writeFile(executablePath, machHeader); + await writeFile(blockedPath, "member contents"); + await chmod(blockedPath, 0); + try { + const target = await parseBinaryTarget(appPath); + expect(target.ok).toBe(true); + if (!target.ok) return; + const result = await new ArtifactProvider() + .createClient(target.value) + .execute("inventory_artifact", {}); + expect(result.ok).toBe(false); + if (result.ok) return; + expect(projectAnalysisError(result.error)).toMatchObject({ + code: "artifact_operation_failed", + details: { + operation: "inventory_artifact", + reason: "io", + }, + }); + const projected = projectAnalysisError(result.error); + const detail = projected.details?.detail; + expect(typeof detail).toBe("string"); + expect(detail).toContain("Denied.app/Contents/blocked.txt"); + expect(detail).toContain("EACCES"); + } finally { + await chmod(blockedPath, 0o600); + } + }, + ); +}); diff --git a/tests/boundary/providers/native/nativeDmgArtifactReader.test.ts b/tests/boundary/providers/native/nativeDmgArtifactReader.test.ts index b04633b96..2b949758d 100644 --- a/tests/boundary/providers/native/nativeDmgArtifactReader.test.ts +++ b/tests/boundary/providers/native/nativeDmgArtifactReader.test.ts @@ -1,9 +1,12 @@ -import { mkdir, realpath, writeFile } from "node:fs/promises"; +import { chmod, mkdir, readFile, realpath, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { build } from "plist"; import { describe, expect, it } from "vitest"; +import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; +import { execFileOutput } from "../../../../src/process/ExecFileOutput.js"; +import { ArtifactReaderFailure } from "../../../../src/artifacts/ArtifactReader.js"; import { NativeDmgArtifactReader, type NativeDmgHost, @@ -53,9 +56,19 @@ describe("native DMG artifact reader", () => { it("rejects non-zero results and surfaces detach failure during attach cleanup", async () => { await expect( NativeDmgArtifactReader.create("/tmp/image.dmg", undefined, { - run: () => Promise.resolve({ stdout: "", exitCode: 1 }), + run: () => + Promise.resolve({ + stdout: "verify output", + stderr: "hdiutil: verify failed - image not recognized", + exitCode: 1, + }), }), - ).rejects.toThrow("non-zero exit code"); + ).rejects.toMatchObject({ + reason: "format", + message: expect.stringMatching( + /"arguments":\["verify","\/tmp\/image\.dmg"\].*"exitCode":1.*"stderr":"hdiutil: verify failed - image not recognized"/u, + ), + }); const host: NativeDmgHost = { run(arguments_) { @@ -91,6 +104,238 @@ describe("native DMG artifact reader", () => { }); }); +describe("native DMG command failure diagnostics", () => { + it("keeps unknown attach failures unavailable with command evidence", async () => { + await expect( + NativeDmgArtifactReader.create("/tmp/image.dmg", undefined, { + run(arguments_) { + if (arguments_[0] === "verify") + return Promise.resolve({ stdout: "", exitCode: 0 }); + return Promise.resolve({ + stdout: "attach output", + stderr: "attach failed for an unknown reason", + exitCode: 2, + }); + }, + }), + ).rejects.toMatchObject({ + reason: "unavailable", + message: expect.stringMatching( + /"arguments":\["attach".*"exitCode":2.*"stderr":"attach failed for an unknown reason"/u, + ), + }); + }); + + it("keeps unrecognized numeric verify failures unknown and maps the observed I/O diagnostic", async () => { + const unknown = await NativeDmgArtifactReader.create( + "/tmp/image.dmg", + undefined, + { + run: () => + Promise.resolve({ + stdout: "verify stdout", + stderr: "hdiutil: verify failed - an unrecognized system failure", + exitCode: 1, + }), + }, + ).catch((cause: unknown) => cause); + expect(unknown).toBeInstanceOf(ArtifactReaderFailure); + if (!(unknown instanceof ArtifactReaderFailure)) return; + expect(unknown.reason).toBe("unavailable"); + expect(unknown.message).toContain("do not establish the failure cause"); + expect(unknown.message).toContain('"exitCode":1'); + expect(unknown.message).toContain('"stdout":"verify stdout"'); + expect(unknown.message).toContain("an unrecognized system failure"); + + await expect( + NativeDmgArtifactReader.create("/tmp/image.dmg", undefined, { + run: () => + Promise.resolve({ + stdout: "", + stderr: "hdiutil: verify failed - Input/output error\n", + exitCode: 1, + }), + }), + ).rejects.toMatchObject({ + reason: "io", + message: expect.stringContaining("Input/output error"), + }); + + await expect( + NativeDmgArtifactReader.create("/tmp/image.dmg", undefined, { + run: () => + Promise.resolve({ + stdout: "checksum output", + stderr: "hdiutil: verify failed - image data corrupted\n", + exitCode: 1, + }), + }), + ).rejects.toMatchObject({ + reason: "integrity", + message: expect.stringContaining("image data corrupted"), + }); + }); + + it("distinguishes child launch and host permission failures", async () => { + await expect( + NativeDmgArtifactReader.create("/tmp/image.dmg", undefined, { + run: () => + Promise.reject( + Object.assign(new Error("could not launch hdiutil"), { + code: "ENOENT", + syscall: "spawn /usr/bin/hdiutil", + }), + ), + }), + ).rejects.toMatchObject({ + reason: "unavailable", + message: expect.stringContaining('"code":"ENOENT"'), + }); + + await expect( + NativeDmgArtifactReader.create("/tmp/image.dmg", undefined, { + run: () => + Promise.reject( + Object.assign(new Error("permission denied"), { + code: "EACCES", + syscall: "spawn /usr/bin/hdiutil", + }), + ), + }), + ).rejects.toMatchObject({ + reason: "io", + message: expect.stringContaining('"code":"EACCES"'), + }); + }); +}); + +describe("native DMG real verification diagnostics", () => { + it.skipIf(process.platform !== "darwin")( + "classifies a real hdiutil verify refusal as a format failure with captured output", + async () => { + const root = await createTestTempDirectory("rea-invalid-dmg-"); + const path = join(root, "invalid.dmg"); + await writeFile(path, "This is not a disk image.\n"); + const failure = await NativeDmgArtifactReader.create(path).catch( + (cause: unknown) => cause, + ); + expect(failure).toBeInstanceOf(ArtifactReaderFailure); + if (!(failure instanceof ArtifactReaderFailure)) return; + expect(failure.reason).toBe("format"); + expect(failure.message).toContain('"command":"/usr/bin/hdiutil"'); + expect(failure.message).toContain('"arguments":["verify"'); + expect(failure.message).toContain('"exitCode":1'); + expect(failure.message).toContain('"stdout":""'); + expect(failure.message).toContain('"stderr":"'); + }, + ); + + it.skipIf(process.platform !== "darwin")( + "classifies real hdiutil target access failures as I/O, separately from malformed images", + async () => { + const root = await createTestTempDirectory("rea-dmg-target-errors-"); + const missingPath = join(root, "missing.dmg"); + const unreadablePath = join(root, "unreadable.dmg"); + await writeFile(unreadablePath, "not a disk image\n"); + await chmod(unreadablePath, 0); + try { + for (const [path, expectedDiagnostic] of [ + [missingPath, "No such file or directory"], + [unreadablePath, "Permission denied"], + ] as const) { + const failure = await NativeDmgArtifactReader.create(path).catch( + (cause: unknown) => cause, + ); + expect(failure).toBeInstanceOf(ArtifactReaderFailure); + if (!(failure instanceof ArtifactReaderFailure)) continue; + expect(failure.reason).toBe("io"); + expect(failure.message).toContain('"command":"/usr/bin/hdiutil"'); + expect(failure.message).toContain('"arguments":["verify"'); + expect(failure.message).toContain('"exitCode":1'); + expect(failure.message).toContain('"stdout":""'); + expect(failure.message).toContain(expectedDiagnostic); + expect(failure.cause).toBeInstanceOf(Error); + if (!(failure.cause instanceof Error)) continue; + expect(Reflect.get(failure.cause, "code")).toBe(1); + expect(Reflect.get(failure.cause, "stderr")).toContain( + expectedDiagnostic, + ); + } + } finally { + await chmod(unreadablePath, 0o600); + } + }, + ); + + it.skipIf(process.platform !== "darwin")( + "classifies a checksum mismatch in an hdiutil-created DMG as an integrity failure", + async () => { + const root = await createTestTempDirectory( + "rea-dmg-checksum-corruption-", + ); + const writableImagePath = join(root, "writable.dmg"); + const imagePath = join(root, "fixture.dmg"); + await execFileOutput("/usr/bin/hdiutil", [ + "create", + "-quiet", + "-size", + "1m", + "-layout", + "NONE", + "-type", + "UDIF", + writableImagePath, + ]); + await execFileOutput("/usr/bin/hdiutil", [ + "convert", + "-quiet", + writableImagePath, + "-format", + "UDRO", + "-o", + imagePath, + ]); + const valid = await execFileOutput("/usr/bin/hdiutil", [ + "verify", + imagePath, + ]); + expect(valid.stderr).toContain("is VALID"); + + const image = await readFile(imagePath); + const footer = image.subarray(-512); + expect(footer.subarray(0, 4).toString("ascii")).toBe("koly"); + const dataForkOffset = Number(footer.readBigUInt64BE(24)); + const dataForkLength = Number(footer.readBigUInt64BE(32)); + expect(dataForkLength).toBeGreaterThan(4096); + const corruptionOffset = dataForkOffset + 4096; + expect(corruptionOffset).toBeLessThan(dataForkOffset + dataForkLength); + const originalByte = image[corruptionOffset]; + if (originalByte === undefined) + throw new Error("expected a byte in the generated image data fork"); + image[corruptionOffset] = originalByte ^ 0x01; + await writeFile(imagePath, image); + + const failure = await NativeDmgArtifactReader.create(imagePath).catch( + (cause: unknown) => cause, + ); + expect(failure).toBeInstanceOf(ArtifactReaderFailure); + if (!(failure instanceof ArtifactReaderFailure)) return; + expect(failure.reason).toBe("integrity"); + expect(failure.message).toContain('"command":"/usr/bin/hdiutil"'); + expect(failure.message).toContain('"arguments":["verify"'); + expect(failure.message).toContain('"exitCode":1'); + expect(failure.message).toContain("calculated CRC32"); + expect(failure.message).toContain("expected CRC32"); + expect(failure.message).toContain("verify failed - invalid checksum"); + expect(failure.cause).toBeInstanceOf(Error); + if (!(failure.cause instanceof Error)) return; + expect(Reflect.get(failure.cause, "code")).toBe(1); + expect(Reflect.get(failure.cause, "stderr")).toContain("is INVALID"); + }, + 60_000, + ); +}); + it("owns the canonical mount root and detaches each observed whole image once", async () => { const calls: string[][] = []; const host: NativeDmgHost = { diff --git a/tests/boundary/providers/native/nativeNumericPermissions.test.ts b/tests/boundary/providers/native/nativeNumericPermissions.test.ts deleted file mode 100644 index 3ca85ec75..000000000 --- a/tests/boundary/providers/native/nativeNumericPermissions.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect, it } from "vitest"; -import { parseOtoolLoadCommands } from "../../../../src/native/parsers/otool.js"; - -it.each([0, 1, 2, 3, 4, 5, 6, 7])( - "decodes Mach VM protection bits for mask %i", - (mask) => { - const parsed = parseOtoolLoadCommands(`Load command 0 - cmd LC_SEGMENT_64 - segname __DATA - maxprot 0x${mask.toString(16)} - initprot ${mask} -`); - for (const permissions of [ - parsed.segments[0]?.maximum_permissions, - parsed.segments[0]?.initial_permissions, - ]) { - expect(permissions).toMatchObject({ - read: (mask & 1) !== 0, - write: (mask & 2) !== 0, - execute: (mask & 4) !== 0, - }); - } - }, -); diff --git a/tests/composition/analysis-sessions/artifactComparison.test.ts b/tests/composition/analysis-sessions/artifactComparison.test.ts index 36fbfaf52..a3850df20 100644 --- a/tests/composition/analysis-sessions/artifactComparison.test.ts +++ b/tests/composition/analysis-sessions/artifactComparison.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; import { fragmentInventoryEvidence } from "../../fixtures/artifactEvidence.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; -import { inventoryArtifact } from "../../../src/application/ArtifactInventory.js"; +import { inventoryArtifact } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js"; import { artifactComparisonResultSchema, diff --git a/tests/composition/analysis-sessions/binarySession.test.ts b/tests/composition/analysis-sessions/binarySession.test.ts index da99e17e8..205692e77 100644 --- a/tests/composition/analysis-sessions/binarySession.test.ts +++ b/tests/composition/analysis-sessions/binarySession.test.ts @@ -3,6 +3,7 @@ import type { AnalysisProvider, } from "../../../src/application/AnalysisProvider.js"; import type { BinarySession } from "../../../src/application/binary/BinarySession.js"; +import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js"; import type { JsonValue } from "../../../src/domain/jsonValue.js"; import type { RecordUnknownInput } from "../../../src/domain/residualUnknown.js"; import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js"; @@ -10,6 +11,8 @@ import { HopperStartError } from "../../../src/domain/hopperErrors.js"; import { ProviderAdapterError } from "../../../src/domain/providerAdapterError.js"; import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js"; import { err, ok as resultOk } from "../../../src/domain/result.js"; +import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js"; +import { createEvidence } from "../../../src/domain/evidence.js"; import { observed as ok } from "../../fixtures/analysisExecution.js"; import { ControllableAnalysisClient, @@ -157,6 +160,65 @@ describe("detached provider and target metadata", () => { }); }); +describe("opening previewed targets", () => { + it("rechecks the active target after another serialized transition", async () => { + const [first, second] = await createBinarySessionTargets(); + const calls: string[] = []; + const session = createTestBinarySession(createCacheProvider(calls)); + try { + expect((await session.open(first)).ok).toBe(true); + const target = session.activeTarget(); + if (target === undefined) throw new Error("Expected an active target"); + const preview = await session.previewTarget(target); + if (!preview.ok) throw preview.error; + expect(preview.value.sameTarget).toBe(true); + expect((await session.open(second)).ok).toBe(true); + expect((await session.openResolvedTarget(preview.value)).ok).toBe(true); + expect(session.activeTarget()?.path).toBe(first); + expect(calls).toEqual(["health", "health", "health"]); + } finally { + await session.close(); + } + }); + + it("rejects cancellation and a staged snapshot changed after preview without startup", async () => { + const [first, second] = await createBinarySessionTargets(); + const calls: string[] = []; + const session = createTestBinarySession(createCacheProvider(calls)); + const other = createTestBinarySession(createCacheProvider([])); + try { + const parsed = await parseBinaryTarget(first); + if (!parsed.ok) throw parsed.error; + const preview = await session.previewTarget(parsed.value); + if (!preview.ok) throw preview.error; + const controller = new AbortController(); + controller.abort(); + const cancelled = await session.openResolvedTarget(preview.value, { + signal: controller.signal, + }); + expect(cancelled).toMatchObject({ + ok: false, + error: { _tag: "AnalysisCancelledError" }, + }); + + expect((await other.open(second)).ok).toBe(true); + const snapshot = other.exportAnalysisSnapshot(); + if (!snapshot.ok) throw snapshot.error; + expect(session.importAnalysisSnapshot(snapshot.value).ok).toBe(true); + const rejected = await session.openResolvedTarget(preview.value); + expect(rejected).toMatchObject({ + ok: false, + error: { _tag: "EvidenceIntegrityError" }, + }); + expect(calls).toEqual([]); + expect(session.activeTarget()).toBeUndefined(); + } finally { + await session.close(); + await other.close(); + } + }); +}); + describe("non-hopper provider dispatch", () => { it("runs through a non-Hopper analysis provider", async () => { const [first] = await createBinarySessionTargets(); @@ -267,6 +329,144 @@ describe("fresh run identity", () => { }); }); +describe("evidence metadata imports and snapshot cache", () => { + it("retains cached queries and exports after additive evidence imports", async () => { + const [target] = await createBinarySessionTargets(); + const calls: string[] = []; + const session = createTestBinarySession(createCacheProvider(calls)); + expect((await session.open(target)).ok).toBe(true); + expect( + ( + await session.execute("address_name", { + address: "0x1000", + document: "first", + }) + ).ok, + ).toBe(true); + const before = session.exportAnalysisSnapshot(); + expect(before.ok).toBe(true); + if (!before.ok) { + await session.close(); + return; + } + const existing = session.exportEvidenceBundle().records[0]; + expect(existing?.subject).not.toBeNull(); + if (existing?.subject === null || existing === undefined) { + await session.close(); + return; + } + const addition = createEvidence( + { + path: existing.subject.local_path, + sha256: existing.subject.digest.sha256, + format: existing.subject.format, + ...(existing.subject.architecture === null + ? {} + : { architecture: existing.subject.architecture }), + }, + { id: "fixture", name: "Fixture", version: "1" }, + { + predicateType: "rea.analysis", + operation: "health", + parameters: { source: "additive-import" }, + result: true, + }, + ); + expect( + session.importEvidenceBundle(createEvidenceBundle([addition])), + ).toEqual({ ok: true, value: 1 }); + expect(session.exportAnalysisSnapshot()).toMatchObject({ + ok: true, + value: { + entries: before.value.entries, + evidence_bundle: { records: expect.arrayContaining([addition]) }, + }, + }); + const callsAfterInitialRead = [...calls]; + expect( + ( + await session.execute("address_name", { + address: "0x1000", + document: "first", + }) + ).ok, + ).toBe(true); + expect(calls).toEqual(callsAfterInitialRead); + expect(session.exportAnalysisSnapshot().ok).toBe(true); + await session.close(); + }); + + it("invalidates cached analysis when an import changes evidence path metadata", async () => { + const [target] = await createBinarySessionTargets(); + const calls: string[] = []; + const session = createTestBinarySession(createCacheProvider(calls)); + expect((await session.open(target)).ok).toBe(true); + expect( + ( + await session.execute("address_name", { + address: "0x1000", + document: "first", + }) + ).ok, + ).toBe(true); + expect(session.exportAnalysisSnapshot().ok).toBe(true); + + const evidence = session + .exportEvidenceBundle() + .records.find(({ operation }) => operation === "address_name"); + expect(evidence?.subject).not.toBeNull(); + if (evidence?.subject === null || evidence === undefined) { + await session.close(); + return; + } + expect( + session.importEvidenceBundle(createEvidenceBundle([evidence])), + ).toEqual({ ok: true, value: 0 }); + expect(session.exportAnalysisSnapshot().ok).toBe(true); + const callsAfterInitialRead = [...calls]; + expect( + ( + await session.execute("address_name", { + address: "0x1000", + document: "first", + }) + ).ok, + ).toBe(true); + expect(calls).toEqual(callsAfterInitialRead); + + const relocated = { + ...evidence, + subject: { + ...evidence.subject, + local_path: `${evidence.subject.local_path}.moved`, + name: `${evidence.subject.name}.moved`, + }, + }; + expect( + session.importEvidenceBundle(createEvidenceBundle([relocated])), + ).toEqual({ ok: true, value: 0 }); + expect(session.exportEvidenceBundle().records).toContainEqual(relocated); + expect( + ( + await session.execute("address_name", { + address: "0x1000", + document: "first", + }) + ).ok, + ).toBe(true); + expect(calls).toHaveLength(callsAfterInitialRead.length + 1); + expect(session.exportAnalysisSnapshot()).toMatchObject({ + ok: false, + error: { + message: expect.stringContaining( + "Analysis snapshots are unavailable after analysis metadata mutations", + ), + }, + }); + await session.close(); + }); +}); + describe("replay of exact immutable calls", () => { it("replays exact immutable calls from a matching provider-neutral snapshot", async () => { const [first, second] = await createBinarySessionTargets(); diff --git a/tests/composition/android/application.test.ts b/tests/composition/android/application.test.ts index fe8def8ad..ad7ff2b94 100644 --- a/tests/composition/android/application.test.ts +++ b/tests/composition/android/application.test.ts @@ -99,6 +99,50 @@ describe("Android application projection", () => { }); }); + it("keeps uppercase path-derived bytecode families distinct from byte validity", async () => { + const root = await createTestTempDirectory("rea-android-suffixes-"); + const path = join(root, "Suffixes.apk"); + const writer = new ZipWriter(new Uint8ArrayWriter()); + for (const entry of [ + "classes.DEX", + "extra.dex", + "Main.CLASS", + "Other.class", + ]) + await writer.add(entry, new TextReader("unrecognized bytes")); + await writeFile(path, await writer.close()); + + const inventory = parseEvidence( + await runProviderAnalysis(path, "inventory_artifact", {}), + ); + const result = projectAndroidApplicationEvidence({ + inventory_evidence: [inventory], + }); + const projection = androidApplicationProjectionResultSchema.parse( + requireSuccessfulProjection(result).normalized_result, + ); + + expect(projection.components.dex.map(({ path }) => path).sort()).toEqual([ + "classes.DEX", + "extra.dex", + ]); + expect( + projection.components.jvm_classes.map(({ path }) => path).sort(), + ).toEqual(["Main.CLASS", "Other.class"]); + expect( + projection.components.dex.every(({ format }) => format === "file"), + ).toBe(true); + expect( + projection.components.jvm_classes.every( + ({ format }) => format === "file", + ), + ).toBe(true); + expect(projection.runtime_families).toEqual(["dalvik-art", "java-kotlin"]); + expect(projection.limitations).toContain( + "Runtime families are inferred from inventory formats and paths; filename suffixes do not establish valid DEX or JVM class bytes.", + ); + }); + it("returns every component and bridge candidate from the inventory", async () => { const root = await createTestTempDirectory("rea-android-complete-"); const path = join(root, "Fixture.apk"); diff --git a/tests/composition/directAnalysisSnapshotReplay.test.ts b/tests/composition/directAnalysisSnapshotReplay.test.ts new file mode 100644 index 000000000..9f5d09ce4 --- /dev/null +++ b/tests/composition/directAnalysisSnapshotReplay.test.ts @@ -0,0 +1,633 @@ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import { runDirectAnalysis } from "../../src/application/DirectAnalysis.js"; +import type { + AnalysisProvider, + CapabilityDescriptor, + ProviderIdentity, +} from "../../src/application/AnalysisProvider.js"; +import type { DirectAnalysisDependencies } from "../../src/application/DirectAnalysisDependencies.js"; +import { createAnalysisProfile } from "../../src/domain/analysisProfile.js"; +import { createAnalysisExecution } from "../../src/application/AnalysisProvider.js"; +import { ok } from "../../src/domain/result.js"; +import { + readAnalysisSnapshot, + writeAnalysisSnapshot, +} from "../../src/application/binary/AnalysisSnapshotFiles.js"; +import { createEvidence, type Evidence } from "../../src/domain/evidence.js"; +import { createEvidenceBundle } from "../../src/domain/evidenceBundle.js"; +import { + REA_WORKFLOW_PROVIDER, + workflowAnalysisProfile, +} from "../../src/application/InvestigationProviders.js"; +import { + createAnalysisSnapshotWorkflowEntry, + type AnalysisSnapshot, +} from "../../src/domain/analysisSnapshot.js"; +import { createTestBinarySession } from "../fixtures/binarySession.js"; +import { BinarySession } from "../../src/application/binary/BinarySession.js"; +import { SessionProviderRouter } from "../../src/application/binary/SessionProviderRouter.js"; +import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; + +const IDENTITY = { + id: "snapshot-fixture", + name: "Snapshot Fixture Provider", + version: "1", +} as const; + +const operations = [ + "list_segments", + "list_documents", + "list_procedures", + "list_strings", +] as const; + +const workflowProviderOperations = [ + ...operations, + "analyze_function", + "list_names", + "search_strings", + "search_procedures", + "xrefs", + "resolve_containing_procedure", +] as const; + +const functionDossier = { + procedure: { + address: "0x1000", + name: "fixture", + signature: null, + locals: [], + }, + pseudocode: "return 0;", + assembly: ["ret"], + comments: [], + callers: [], + callees: [], + incoming_references: [], + outgoing_references: [], + referenced_strings: [], + referenced_names: [], + basic_blocks: [], + native_api: null, + native_value_flow: null, + limitations: [], +}; + +const makeProvider = ( + starts: string[], + calls: string[], + profile = createAnalysisProfile(IDENTITY, { fixture: true }), + identity: ProviderIdentity = profile.provider, + supportedOperations: readonly CapabilityDescriptor["operation"][] = operations, + resolutions: string[] = [], +): AnalysisProvider => { + const capabilities: CapabilityDescriptor[] = supportedOperations.map( + (operation) => ({ + operation, + provider: identity, + available: true, + reason: null, + cachePolicy: "snapshot", + effects: { + mutatesArtifact: false, + launchesProcess: true, + mayShowUi: false, + mayAccessNetwork: false, + mayWriteFilesystem: false, + changesPermissions: false, + requiresRoot: false, + }, + limitations: [], + }), + ); + return { + identity: () => identity, + capabilities: () => capabilities, + resolveAnalysisProfile: async () => { + resolutions.push(profile.digest); + return ok({ profile, compatibility: {} }); + }, + createClient: () => { + starts.push("start"); + return { + execute: async (operation) => { + calls.push(operation); + const results: Readonly> = { + list_segments: [{ name: "__TEXT", start: "0x1000", end: "0x2000" }], + list_documents: ["fixture"], + list_procedures: ["0x1000"], + list_strings: { "0x1000": "fixture" }, + analyze_function: functionDossier, + list_names: [{ address: "0x1000", name: "fixture" }], + search_strings: [{ address: "0x1000", value: "fixture" }], + search_procedures: [{ address: "0x1000", value: "fixture" }], + xrefs: [], + resolve_containing_procedure: null, + procedure_pseudo_code: "return 0;", + }; + const result = results[operation] ?? null; + return ok( + createAnalysisExecution(result, profile.provider, { + analysisProfile: profile, + rawResult: result, + }), + ); + }, + close: async () => undefined, + }; + }, + }; +}; + +const withAlternateWorkflowProfile = ( + snapshot: AnalysisSnapshot, + path: string, + current: Evidence, +): AnalysisSnapshot => { + const alternateProfile = createAnalysisProfile(REA_WORKFLOW_PROVIDER, { + workflow: "binary_overview", + fixture: "alternate-profile", + }); + const subject = { + path, + sha256: snapshot.target.sha256, + format: snapshot.target.format, + ...(snapshot.target.architecture === null + ? {} + : { architecture: snapshot.target.architecture }), + }; + const alternateEvidence = createEvidence(subject, REA_WORKFLOW_PROVIDER, { + operation: current.operation, + parameters: current.parameters, + result: current.normalized_result, + rawResult: current.raw_result, + analysisProfile: alternateProfile, + confidence: "derived", + limitations: current.limitations, + locations: current.locations, + }); + const alternateEntry = createAnalysisSnapshotWorkflowEntry({ + target: snapshot.target, + binding: snapshot.binding, + operation: current.operation, + parameters: current.parameters, + execution: { + result: current.normalized_result, + rawResult: current.raw_result, + provider: REA_WORKFLOW_PROVIDER, + analysisProfile: alternateProfile, + limitations: current.limitations, + locations: current.locations, + subject, + }, + }); + return { + ...snapshot, + workflow_entries: [alternateEntry], + evidence_bundle: createEvidenceBundle([ + ...snapshot.evidence_bundle.records, + alternateEvidence, + ]), + }; +}; + +const withEarlierHistoricalEvidence = ( + snapshot: AnalysisSnapshot, + path: string, + current: Evidence, +): AnalysisSnapshot => { + const older = Array.from({ length: 64 }, (_, index) => + createEvidence( + { + path, + sha256: snapshot.target.sha256, + format: "analysis-database", + }, + REA_WORKFLOW_PROVIDER, + { + operation: "binary_overview", + parameters: {}, + result: `historical-${index}`, + analysisProfile: workflowAnalysisProfile( + snapshot.binding.analysis_profile, + ), + confidence: "derived", + limitations: ["Derived by an REA composed workflow."], + }, + ), + ).find((record) => record.evidence_id < current.evidence_id); + if (older === undefined) + throw new Error("fixture could not construct earlier historical Evidence"); + return { + ...snapshot, + evidence_bundle: createEvidenceBundle([ + ...snapshot.evidence_bundle.records, + older, + ]), + }; +}; + +describe("direct analysis composed snapshot replay", () => { + it.each([ + { + tool: "search_strings", + arguments: { pattern: "uncached", document: "fixture" }, + }, + { tool: "inspect_native_api", arguments: { procedure: "0x2000" } }, + ] as const)( + "reuses the previewed profile on a $tool snapshot miss", + async (scenario) => { + const directory = await createTestTempDirectory( + "rea-snapshot-miss-route-", + ); + const path = join(directory, "fixture.hop"); + const snapshotPath = join(directory, "snapshot.json"); + await writeFile(path, "fixture"); + const starts: string[] = []; + const calls: string[] = []; + const resolutions: string[] = []; + const profile = createAnalysisProfile(IDENTITY, { fixture: true }); + const provider = makeProvider( + starts, + calls, + profile, + IDENTITY, + workflowProviderOperations, + resolutions, + ); + const dependencies: DirectAnalysisDependencies = { + createBinarySession: () => createTestBinarySession(provider), + createManagedBinarySession: () => createTestBinarySession(provider), + }; + await runDirectAnalysis( + dependencies, + path, + "binary_overview", + {}, + { snapshotPath }, + ); + expect(resolutions).toHaveLength(1); + + const first = await runDirectAnalysis( + dependencies, + path, + scenario.tool, + scenario.arguments, + { snapshotPath }, + ); + expect(first).toMatchObject({ operation: scenario.tool }); + expect(resolutions).toHaveLength(2); + expect(starts).toHaveLength(2); + const callsAfterMiss = [...calls]; + + const replay = await runDirectAnalysis( + dependencies, + path, + scenario.tool, + scenario.arguments, + { snapshotPath }, + ); + expect(replay).toEqual(first); + expect(resolutions).toHaveLength(3); + expect(starts).toHaveLength(2); + expect(calls).toEqual(callsAfterMiss); + }, + ); + + for (const scenario of [ + { + tool: "inspect_native_api", + arguments: { procedure: "0x1000" }, + }, + { + tool: "inspect_native_dispatch_metadata", + arguments: {}, + }, + { + tool: "trace_feature", + arguments: { query: "fixture" }, + }, + { + tool: "trace_native_values", + arguments: { procedure: "0x1000" }, + }, + ] as const) { + it(`persists and replays ${scenario.tool} without provider startup`, async () => { + const directory = await createTestTempDirectory("rea-workflow-command-"); + const path = join(directory, "fixture.hop"); + const snapshotPath = join(directory, "snapshot.json"); + await writeFile(path, "fixture"); + const starts: string[] = []; + const calls: string[] = []; + const provider = makeProvider( + starts, + calls, + createAnalysisProfile(IDENTITY, { fixture: true }), + IDENTITY, + workflowProviderOperations, + ); + const dependencies: DirectAnalysisDependencies = { + createBinarySession: () => createTestBinarySession(provider), + createManagedBinarySession: () => createTestBinarySession(provider), + }; + const first = await runDirectAnalysis( + dependencies, + path, + scenario.tool, + scenario.arguments, + { snapshotPath }, + ); + const callsAfterFirst = [...calls]; + const startsAfterFirst = [...starts]; + const loaded = await readAnalysisSnapshot(snapshotPath); + if (!loaded.ok) throw loaded.error; + expect( + loaded.value.workflow_entries?.map(({ operation }) => operation), + ).toContain(scenario.tool); + + const second = await runDirectAnalysis( + dependencies, + path, + scenario.tool, + scenario.arguments, + { snapshotPath }, + ); + + expect(second).toEqual(first); + expect(calls).toEqual(callsAfterFirst); + expect(starts).toEqual(startsAfterFirst); + }); + } +}); + +describe("binary overview snapshot replay", () => { + it("replays the identical binary overview without provider startup or calls", async () => { + const directory = await createTestTempDirectory("rea-workflow-snapshot-"); + const path = join(directory, "fixture.hop"); + const snapshotPath = join(directory, "snapshot.json"); + await writeFile(path, "fixture"); + const starts: string[] = []; + const calls: string[] = []; + const provider = makeProvider(starts, calls); + const dependencies: DirectAnalysisDependencies = { + createBinarySession: () => createTestBinarySession(provider), + createManagedBinarySession: () => createTestBinarySession(provider), + }; + + const first = await runDirectAnalysis( + dependencies, + path, + "binary_overview", + {}, + { snapshotPath }, + ); + expect(calls).toEqual(["health", ...operations]); + expect(starts).toHaveLength(1); + + const loaded = await readAnalysisSnapshot(snapshotPath); + if (!loaded.ok) throw loaded.error; + expect(loaded.value.workflow_entries).toHaveLength(1); + const current = loaded.value.evidence_bundle.records.find( + (record) => record.operation === "binary_overview", + ); + if (current === undefined) + throw new Error("composed Evidence was not saved"); + expect( + ( + await writeAnalysisSnapshot( + withEarlierHistoricalEvidence(loaded.value, path, current), + snapshotPath, + true, + ) + ).ok, + ).toBe(true); + + const second = await runDirectAnalysis( + dependencies, + path, + "binary_overview", + {}, + { snapshotPath }, + ); + expect(second).toEqual(first); + expect(calls).toEqual(["health", ...operations]); + expect(starts).toHaveLength(1); + }); +}); + +describe("snapshot replay provider identity", () => { + it("uses the resolved provider version when capability metadata omits it", async () => { + const directory = await createTestTempDirectory("rea-profile-version-"); + const path = join(directory, "fixture.hop"); + const snapshotPath = join(directory, "snapshot.json"); + await writeFile(path, "fixture"); + const starts: string[] = []; + const calls: string[] = []; + const unresolvedIdentity = { ...IDENTITY, version: null }; + const resolvedProfile = createAnalysisProfile( + { ...IDENTITY, version: "launcher-sha256:fixture" }, + { fixture: true }, + ); + const provider = makeProvider( + starts, + calls, + resolvedProfile, + unresolvedIdentity, + ["search_strings"], + ); + const dependencies: DirectAnalysisDependencies = { + createBinarySession: () => createTestBinarySession(provider), + createManagedBinarySession: () => createTestBinarySession(provider), + }; + const first = await runDirectAnalysis( + dependencies, + path, + "search_strings", + { pattern: "fixture", document: "fixture" }, + { snapshotPath }, + ); + const callsAfterFirst = [...calls]; + const startsAfterFirst = [...starts]; + const firstSnapshot = await readAnalysisSnapshot(snapshotPath); + if (!firstSnapshot.ok) throw firstSnapshot.error; + expect(firstSnapshot.value.entries[0]?.execution.provider.version).toBe( + "launcher-sha256:fixture", + ); + + const second = await runDirectAnalysis( + dependencies, + path, + "search_strings", + { pattern: "fixture", document: "fixture" }, + { snapshotPath }, + ); + expect(calls).toEqual(callsAfterFirst); + expect(starts).toEqual(startsAfterFirst); + expect(second).toEqual(first); + + const changedProfileProvider = makeProvider( + starts, + calls, + createAnalysisProfile( + { ...IDENTITY, version: "launcher-sha256:changed" }, + { fixture: true }, + ), + unresolvedIdentity, + ["search_strings"], + ); + const changedProfileResult = await runDirectAnalysis( + { + createBinarySession: () => + createTestBinarySession(changedProfileProvider), + createManagedBinarySession: () => + createTestBinarySession(changedProfileProvider), + }, + path, + "search_strings", + { pattern: "fixture", document: "fixture" }, + { snapshotPath }, + ); + expect(changedProfileResult).not.toEqual(first); + expect(changedProfileResult).toMatchObject({ + code: "evidence_integrity_mismatch", + }); + expect(starts).toEqual(startsAfterFirst); + expect(calls).toEqual(callsAfterFirst); + + const changedProviderIdentity = { + id: "other-provider", + name: "Other Provider", + version: null, + }; + const changedProvider = makeProvider( + starts, + calls, + createAnalysisProfile( + { ...changedProviderIdentity, version: "other-version" }, + { fixture: true }, + ), + changedProviderIdentity, + ["search_strings"], + ); + const changedProviderResult = await runDirectAnalysis( + { + createBinarySession: () => createTestBinarySession(changedProvider), + createManagedBinarySession: () => + createTestBinarySession(changedProvider), + }, + path, + "search_strings", + { pattern: "fixture", document: "fixture" }, + { snapshotPath }, + ); + expect(changedProviderResult).not.toEqual(first); + expect(changedProviderResult).toMatchObject({ + code: "evidence_integrity_mismatch", + }); + expect(starts).toEqual(startsAfterFirst); + expect(calls).toEqual(callsAfterFirst); + }); +}); + +describe("workflow snapshot profile and cancellation binding", () => { + it("does not replay a valid entry from another workflow profile", async () => { + const directory = await createTestTempDirectory("rea-workflow-profile-"); + const path = join(directory, "fixture.hop"); + const snapshotPath = join(directory, "snapshot.json"); + await writeFile(path, "fixture"); + const starts: string[] = []; + const calls: string[] = []; + const provider = makeProvider(starts, calls); + const dependencies: DirectAnalysisDependencies = { + createBinarySession: () => createTestBinarySession(provider), + createManagedBinarySession: () => createTestBinarySession(provider), + }; + await runDirectAnalysis( + dependencies, + path, + "binary_overview", + {}, + { snapshotPath }, + ); + const loaded = await readAnalysisSnapshot(snapshotPath); + if (!loaded.ok) throw loaded.error; + const current = loaded.value.evidence_bundle.records.find( + (record) => record.operation === "binary_overview", + ); + if (current === undefined) + throw new Error("composed Evidence was not saved"); + expect( + ( + await writeAnalysisSnapshot( + withAlternateWorkflowProfile(loaded.value, path, current), + snapshotPath, + true, + ) + ).ok, + ).toBe(true); + + await runDirectAnalysis( + dependencies, + path, + "binary_overview", + {}, + { snapshotPath }, + ); + expect(calls).toEqual(["health", ...operations, "health", ...operations]); + expect(starts).toHaveLength(2); + }); + + it("does not return a cached result when cancelled during route resolution", async () => { + const directory = await createTestTempDirectory("rea-workflow-cancel-"); + const path = join(directory, "fixture.hop"); + const snapshotPath = join(directory, "snapshot.json"); + await writeFile(path, "fixture"); + const starts: string[] = []; + const calls: string[] = []; + const profile = createAnalysisProfile(IDENTITY, { fixture: true }); + const provider = makeProvider(starts, calls, profile); + const initialDependencies: DirectAnalysisDependencies = { + createBinarySession: () => createTestBinarySession(provider), + createManagedBinarySession: () => createTestBinarySession(provider), + }; + await runDirectAnalysis( + initialDependencies, + path, + "binary_overview", + {}, + { snapshotPath }, + ); + + const controller = new AbortController(); + const router = SessionProviderRouter.single(provider); + const resolve = router.resolve.bind(router); + router.resolve = async (...arguments_) => { + const resolved = await resolve(...arguments_); + controller.abort(); + return resolved; + }; + const session = new BinarySession(router); + const dependencies: DirectAnalysisDependencies = { + createBinarySession: () => session, + createManagedBinarySession: () => session, + }; + const result = await runDirectAnalysis( + dependencies, + path, + "binary_overview", + {}, + { snapshotPath, signal: controller.signal }, + ); + + expect(result).toMatchObject({ + error: "Analysis failed", + code: "cancelled", + }); + expect(calls).toEqual(["health", ...operations]); + expect(starts).toHaveLength(1); + }); +}); diff --git a/tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts b/tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts index d4d60e83b..7dad88ab9 100644 --- a/tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts +++ b/tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts @@ -7,7 +7,7 @@ import { expect, it } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { reconstructJavaScriptArtifact } from "../../../src/application/javascript/JavaScriptArtifactReconstruction.js"; -import { scanArtifactInventory } from "../../../src/application/ArtifactInventory.js"; +import { scanArtifactInventory } from "../../../src/artifacts/inventory/ArtifactInventory.js"; import { parseJavaScriptApplicationGraph } from "../../../src/domain/javascript/javascriptApplicationGraph.js"; import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js"; import { createJavaScriptSemanticGraph } from "../../../src/domain/javascript/javascriptSemanticGraph.js"; diff --git a/tests/conformance/contracts/analysisErrorProjection.test.ts b/tests/conformance/contracts/analysisErrorProjection.test.ts index 228cd7ca8..74c6622cd 100644 --- a/tests/conformance/contracts/analysisErrorProjection.test.ts +++ b/tests/conformance/contracts/analysisErrorProjection.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { ProcessCaptureError } from "../../../src/application/ProcessCaptureError.js"; +import { ProcessCaptureError } from "../../../src/process/capture/ProcessCaptureError.js"; import { analysisErrorProjectionSchema } from "../../../src/contracts/errorSchemas.js"; import { ArtifactOperationError } from "../../../src/domain/artifactOperationError.js"; import { EvidenceFileError } from "../../../src/domain/evidenceErrors.js"; diff --git a/tests/conformance/native/keyed-archive-hierarchy.swift b/tests/conformance/native/keyed-archive-hierarchy.swift new file mode 100644 index 000000000..a562257de --- /dev/null +++ b/tests/conformance/native/keyed-archive-hierarchy.swift @@ -0,0 +1,17 @@ +import AppKit + +let count = Int(CommandLine.arguments[2]) ?? 40 +let root = NSView(frame: .zero) +root.identifier = NSUserInterfaceItemIdentifier("root") +var parent = root +for index in 0.. = captureChildValues(requestedCount: 2) { nil } precondition(failed.values.isEmpty, "AX retrieval failure must return no values") precondition(!failed.complete, "AX retrieval failure must mark traversal incomplete") @@ -15,4 +40,4 @@ let complete = captureChildValues(requestedCount: 2) { [10, 20, 30] } precondition(complete.values == [10, 20], "Retrieval must respect the requested node budget") precondition(complete.complete, "A full bounded result must remain complete") -print("Native UI child retrieval seam passed") +print("Native UI child retrieval and serialization seams passed") diff --git a/tests/conformance/providers/conformanceFixtures.test.ts b/tests/conformance/providers/conformanceFixtures.test.ts deleted file mode 100644 index 4bb5ca6ee..000000000 --- a/tests/conformance/providers/conformanceFixtures.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { sourceDigest } from "../../../scripts/lib/conformance-fixtures.mjs"; - -describe("source-built conformance fixtures", () => { - it("hashes source manifests independently of input ordering", () => { - const left = [ - { path: "b.c", content: "b" }, - { path: "a.c", content: "a" }, - ]; - const right = [...left].reverse(); - - expect(sourceDigest(left)).toBe(sourceDigest(right)); - expect(sourceDigest(left)).toMatch(/^[0-9a-f]{64}$/u); - }); -}); diff --git a/tests/conformance/providers/conformanceTrustGate.test.ts b/tests/conformance/providers/conformanceTrustGate.test.ts index 4de72fb96..bb581b8d3 100644 --- a/tests/conformance/providers/conformanceTrustGate.test.ts +++ b/tests/conformance/providers/conformanceTrustGate.test.ts @@ -4,8 +4,6 @@ import type { VerifierContract } from "../../../src/domain/conformancePackage.js import { compareDimension, evaluateTrustGate, - isSemanticDimension, - isVolatileDimension, trustGateResultSchema, } from "../../../src/domain/conformanceTrustGate.js"; @@ -35,52 +33,6 @@ const expectDivergenceRejected = ( }; describe("conformance trust gates", () => { - it("classifies volatile dimensions correctly", () => { - expect(isVolatileDimension("timing")).toBe(true); - expect(isVolatileDimension("timestamp")).toBe(true); - expect(isVolatileDimension("pid")).toBe(true); - expect(isVolatileDimension("exit_code")).toBe(false); - }); - - it("classifies semantic dimensions correctly", () => { - expect(isSemanticDimension("exit_code")).toBe(true); - expect(isSemanticDimension("stdout")).toBe(true); - expect(isSemanticDimension("timing")).toBe(false); - }); - - it("matches identical values", () => { - const result = compareDimension("exit_code", 0, 0); - expect(result.status).toBe("match"); - }); - - it("detects mismatches", () => { - const result = compareDimension("exit_code", 0, 1); - expect(result.status).toBe("mismatch"); - }); - - it("ignores volatile dimensions", () => { - const result = compareDimension("timing", 100, 200); - expect(result.status).toBe("match"); - expect(result.message).toContain("volatile"); - }); - - it("handles truncated evidence", () => { - const result = compareDimension("exit_code", 0, null, { - truncated: true, - }); - expect(result.status).toBe("truncated"); - }); - - it("handles undefined values", () => { - const result = compareDimension("exit_code", undefined, undefined); - expect(result.status).toBe("unknown"); - }); - - it("handles missing actual", () => { - const result = compareDimension("exit_code", 0, undefined); - expect(result.status).toBe("unknown"); - }); - it("keeps semantic unknown and truncated values out of equivalence", () => { expect( compareDimension( diff --git a/tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts b/tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts deleted file mode 100644 index 8d8573025..000000000 --- a/tests/conformance/providers/ghidra/ghidraWindowsFixture.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { execFile } from "node:child_process"; -import { createHash } from "node:crypto"; -import { readFile } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { promisify } from "node:util"; -import { fileURLToPath } from "node:url"; - -import { describe, expect, it } from "vitest"; - -import { parseExecutableHeader } from "../../../../src/domain/binaryTarget.js"; - -const exec = promisify(execFile); -const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../../.."); -const generator = resolve(root, "scripts", "create-ghidra-windows-fixture.mjs"); -const fixture = resolve(root, "build", "fixtures", "rea-ghidra-windows.exe"); -const expectedSha256 = - "732ec6083ae3b63bcbda33f2d0fd7e1a6539babade07cbdae4fc20d3394faf49"; - -describe("controlled Windows Ghidra fixture", () => { - it("generates one deterministic native x86-64 PE application", async () => { - await exec(process.execPath, [generator]); - const bytes = await readFile(fixture); - - expect(createHash("sha256").update(bytes).digest("hex")).toBe( - expectedSha256, - ); - expect(parseExecutableHeader(bytes, "x64")).toMatchObject({ - ok: true, - value: { - format: "pe", - architecture: "x86_64", - executableRole: "application", - managed: false, - }, - }); - }); -}); diff --git a/tests/conformance/providers/managed/managedConformanceFixture.test.ts b/tests/conformance/providers/managed/managedConformanceFixture.test.ts deleted file mode 100644 index 4899a3dec..000000000 --- a/tests/conformance/providers/managed/managedConformanceFixture.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { rm, writeFile } from "node:fs/promises"; -import { join } from "node:path"; - -import { afterEach, describe, expect, it } from "vitest"; - -import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; - -import { buildManagedPeFixture } from "../../../../scripts/fixtures/managed/pe.mjs"; -import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js"; - -const workspaces: string[] = []; - -afterEach(async () => { - await Promise.all( - workspaces.splice(0).map((path) => rm(path, { recursive: true })), - ); -}); - -describe("managed conformance fixture", () => { - it("emits PE32+ headers when the COFF machine is x86-64", async () => { - const workspace = await createTestTempDirectory("rea-managed-fixture-"); - workspaces.push(workspace); - const path = join(workspace, "fixture.exe"); - await writeFile(path, buildManagedPeFixture({ machine: 0x8664 })); - - const parsed = await parseBinaryTarget(path); - - expect(parsed).toMatchObject({ - ok: true, - value: { - format: "pe", - architecture: "x86_64", - managed: true, - }, - }); - }); -}); diff --git a/tests/conformance/readmeConsistency.test.ts b/tests/conformance/readmeConsistency.test.ts index 7ff09e409..42dfe1db8 100644 --- a/tests/conformance/readmeConsistency.test.ts +++ b/tests/conformance/readmeConsistency.test.ts @@ -5,8 +5,8 @@ import { describe, expect, it } from "vitest"; import { SUPPORTED_CLIENT_DEFINITIONS } from "../../src/application/SupportedClients.js"; import { PRODUCT_IDENTITY } from "../../src/identity.js"; -const readmes = [ - "README.md", +const setupDocumentation = [ + "docs/installation.md", "README_zh.md", "README_ja.md", "README_ko.md", @@ -21,8 +21,8 @@ const jsonExamples = (content: string): unknown[] => JSON.parse(match[1] ?? ""), ); -describe("localized README product facts", () => { - it.each(readmes)( +describe("onboarding documentation product facts", () => { + it.each(setupDocumentation)( "keeps requirements and versioned MCP configuration aligned in %s", async (path) => { const content = await readFile(resolve(path), "utf8"); @@ -45,9 +45,11 @@ describe("localized README product facts", () => { expect(content).toContain("Ubuntu 24.04"); expect(content).toContain("Fedora 41"); expect(content).toContain("Arch Linux"); + expect(content).toContain("CachyOS"); for (const client of SUPPORTED_CLIENT_DEFINITIONS) expect(content).toContain(client.displayName); - expect(content).toContain("MCP-tool_catalog"); + if (path.startsWith("README_")) + expect(content).toContain("MCP-tool_catalog"); }, ); diff --git a/tests/conformance/releaseConfiguration.test.ts b/tests/conformance/releaseConfiguration.test.ts index 6988de6f9..abe6bb478 100644 --- a/tests/conformance/releaseConfiguration.test.ts +++ b/tests/conformance/releaseConfiguration.test.ts @@ -8,7 +8,6 @@ const readJson = async (path: string): Promise => JSON.parse(await readFile(path, "utf8")) as unknown; const versionedDocumentation = [ - "README.md", "README_zh.md", "README_ja.md", "README_ko.md", @@ -63,15 +62,33 @@ describe("release configuration", () => { "marks the versioned package example in %s", async (path) => { const content = await readFile(path, "utf8"); + const packageJson = (await readJson("package.json")) as { + version: string; + }; const versionBlock = /[\s\S]*?/u.exec( content, )?.[0]; - expect(versionBlock).toMatch(/rea-agents@\d+\.\d+\.\d+/u); + expect(versionBlock).toContain(`rea-agents@${packageJson.version}`); }, ); + it("keeps the release baseline, lockfile, and changelog aligned with the package version", async () => { + const packageJson = (await readJson("package.json")) as { + version: string; + }; + expect(await readJson(".release-please-manifest.json")).toEqual({ + ".": packageJson.version, + }); + expect(await readJson("package-lock.json")).toMatchObject({ + version: packageJson.version, + packages: { "": { version: packageJson.version } }, + }); + const changelog = await readFile("CHANGELOG.md", "utf8"); + expect(/^## \[([^\]]+)\]/mu.exec(changelog)?.[1]).toBe(packageJson.version); + }); + it("keeps the npm package and MCP Registry metadata aligned", async () => { const packageJson = (await readJson("package.json")) as { name: string; diff --git a/tests/evaluation/model-evals/codexAgentEval.test.ts b/tests/evaluation/model-evals/codexAgentEval.test.ts index c068878f6..82100cea6 100644 --- a/tests/evaluation/model-evals/codexAgentEval.test.ts +++ b/tests/evaluation/model-evals/codexAgentEval.test.ts @@ -100,6 +100,29 @@ describe("Codex agent release evaluation", () => { .repeatedCallCount, ).toBe(1); }); + + it("counts identical arguments with reversed Unicode key insertion order", () => { + const call = (id: string, arguments_: Record) => ({ + type: "item.completed", + item: { + id, + type: "mcp_tool_call", + server: "rea", + tool: "inspect_artifact", + arguments: arguments_, + }, + }); + + expect( + evaluateCodexEvents( + [ + call("item-1", { "e\u0301": 1, "\u00e9": 2 }), + call("item-2", { "\u00e9": 2, "e\u0301": 1 }), + ], + "inspect_artifact", + ).repeatedCallCount, + ).toBe(1); + }); }); describe("Codex agent completion evaluation", () => { diff --git a/tests/evaluation/moduleBoundaries.test.ts b/tests/evaluation/moduleBoundaries.test.ts index 8dca27063..c9b1a25c8 100644 --- a/tests/evaluation/moduleBoundaries.test.ts +++ b/tests/evaluation/moduleBoundaries.test.ts @@ -8,6 +8,28 @@ import { describe, expect, it } from "vitest"; import { inspectModuleBoundaries } from "../../scripts/lib/module-boundaries.mjs"; import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; +describe("process capture implementation ownership", () => { + it.each([ + ["../../application/process/ProcessEvidence.js", false], + ["../../composition/binary.js", false], + ["../../server/createServer.js", false], + ["../../cli.ts", false], + ["../../main.js", false], + ["../../domain/process/processEvidenceProvider.js", true], + ["../ProcessOwnership.js", true], + ["../../windows/WindowsOwnedProcess.js", true], + ["./TerminalRenderer.js", true], + ])("checks capture importing %s", (dependency, allowed) => { + const violations = inspectModuleBoundaries( + "src/process/capture/ProcessHarness.ts", + `import { value } from "${dependency}";`, + process.cwd(), + ); + if (allowed) expect(violations).toEqual([]); + else expect(violations).toMatchObject([{ boundary: "process-capture" }]); + }); +}); + describe("binary production construction ownership", () => { it.each([ "hopper/HopperProvider.js", @@ -54,7 +76,7 @@ describe("incremental module import boundaries", () => { ["src/contracts/probe.ts", "../composition/android.js", false], [ "src/artifacts/javascript/probe.ts", - "../../application/ArtifactInventory.js", + "../../application/javascript/JavaScriptArtifactReconstruction.js", false, ], [ @@ -65,7 +87,7 @@ describe("incremental module import boundaries", () => { ["src/artifacts/javascript/probe.ts", "../../cli.js", false], [ "src/artifacts/ArtifactHash.ts", - "../application/ArtifactInventory/hash.js", + "../application/artifacts/ArtifactExtractionDestination.js", false, ], [ @@ -89,11 +111,7 @@ describe("incremental module import boundaries", () => { "../../artifacts/javascript/JavaScriptArtifactReader.js", true, ], - [ - "src/application/ArtifactInventory/probe.ts", - "../../artifacts/AsarArtifactReader.js", - true, - ], + ["src/artifacts/inventory/probe.ts", "../AsarArtifactReader.js", true], ["src/domain/probe.test.ts", "../composition/firmware.js", false], ["src/application/probe.ts", "../android/JadxProvider.js", false], ["src/application/probe.ts", "../composition/android.js", false], @@ -307,21 +325,30 @@ describe("test lane import boundaries", () => { }); }); -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(), - ); +describe.each(["apple", "inventory", "extraction"])( + "%s artifact producer ownership", + (owner) => { + it.each([ + ["../../application/Workflow.js", false], + ["../../composition/android.js", false], + ["../../cli.js", false], + ["../../server/registerEvidenceTools.js", false], + ["../../main.ts", false], + ["../../domain/apple/plistValue.js", true], + ["../../domain/artifactInventorySnapshot.js", true], + ["../../contracts/artifactGraph.js", true], + ["../DirectoryArtifactReader.js", true], + ["../ArtifactHash.js", true], + ])("checks the producer dependency %s", (dependency, allowed) => { + const violations = inspectModuleBoundaries( + `src/artifacts/${owner}/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"); - }); -}); + expect(violations.length === 0).toBe(allowed); + if (!allowed) + expect(violations[0]?.boundary).toBe("artifact-acquisition"); + }); + }, +); diff --git a/tests/evaluation/package/packageSetupSupport.test.ts b/tests/evaluation/package/packageSetupSupport.test.ts deleted file mode 100644 index 2d803ef58..000000000 --- a/tests/evaluation/package/packageSetupSupport.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { packageSetupSupport } from "../../../scripts/verify-package-discovery.mjs"; - -describe("packaged setup support classification", () => { - it.each([ - ["linux", true, true, true, true], - ["linux", true, false, true, false], - ["linux", false, true, false, false], - ["linux", false, false, false, false], - ["darwin", true, true, true, true], - ["darwin", true, false, true, false], - ["darwin", false, true, false, false], - ["darwin", false, false, false, false], - ["win32", true, true, true, false], - ["win32", true, false, true, false], - ["win32", false, true, false, false], - ["win32", false, false, false, false], - ] as const)( - "%s with node=%s and host=%s: agent setup=%s, Hopper setup=%s", - (platform, node, host, supportedSetupHost, hopperSetupSupported) => { - expect( - packageSetupSupport( - [ - { name: "host", ok: host }, - { name: "node", ok: node }, - ], - platform, - ), - ).toEqual({ supportedSetupHost, hopperSetupSupported }); - }, - ); - - it.each([ - { checks: undefined }, - { checks: [] }, - { checks: [{ name: "host", ok: true }] }, - ])("requires an affirmative Node check: $checks", ({ checks }) => { - expect(packageSetupSupport(checks, "linux")).toEqual({ - supportedSetupHost: false, - hopperSetupSupported: false, - }); - }); - - it("requires an affirmative host check only for Hopper setup", () => { - expect( - packageSetupSupport( - [ - { name: "node", ok: true }, - { name: "hopper", ok: true }, - ], - "linux", - ), - ).toEqual({ supportedSetupHost: true, hopperSetupSupported: false }); - }); -}); diff --git a/tests/fixtures/abandonStoreFileLock.mjs b/tests/fixtures/abandonStoreFileLock.mjs deleted file mode 100644 index 1022261f1..000000000 --- a/tests/fixtures/abandonStoreFileLock.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { createStoreFileLock } from "../../dist/application/StoreFileLock.js"; - -const path = process.argv[2]; -if (path === undefined) throw new Error("A lock path is required"); -await createStoreFileLock(path); -// Exit without invoking release so the parent can exercise dead-owner recovery. diff --git a/tests/fixtures/fakeCdpBrowserEvents.ts b/tests/fixtures/fakeCdpBrowserEvents.ts index 3a2c5bde8..7d54e66bf 100644 --- a/tests/fixtures/fakeCdpBrowserEvents.ts +++ b/tests/fixtures/fakeCdpBrowserEvents.ts @@ -282,6 +282,133 @@ const emitSessionTimeline = ( } }; +const emitSameOriginRedirectEvents = ( + socket: WebSocket, + command: FakeCdpCommand, + port: number, + url: string, +): string => { + const intermediate = `http://127.0.0.1:${String(port)}/intermediate`; + const finalUrl = `http://127.0.0.1:${String(port)}/redirected?token=final-secret`; + event(socket, "Network.requestWillBeSent", command.sessionId, { + requestId: "request-1", + type: "Fetch", + request: { url: intermediate, method: "GET" }, + redirectResponse: { + url, + status: 301, + mimeType: "text/plain", + encodedDataLength: 23, + headers: { "X-Private": "redirect-header-secret" }, + }, + timestamp: 8, + }); + event(socket, "Network.requestWillBeSent", command.sessionId, { + requestId: "request-1", + type: "Fetch", + request: { url: finalUrl, method: "GET" }, + redirectResponse: { + url: intermediate, + status: 302, + mimeType: "text/plain", + encodedDataLength: 27, + }, + timestamp: 9, + }); + return finalUrl; +}; + +const emitReturnFromDisallowedRedirect = ( + socket: WebSocket, + command: FakeCdpCommand, + port: number, +): string => { + const finalUrl = `http://127.0.0.1:${String(port)}/returned`; + event(socket, "Network.requestWillBeSent", command.sessionId, { + requestId: "request-1", + type: "Fetch", + request: { url: finalUrl, method: "GET" }, + redirectResponse: { + url: "https://private.example.test/outside?token=redirect-url-secret", + status: 302, + mimeType: "text/plain", + encodedDataLength: 31, + headers: { "X-Private": "redirect-header-secret" }, + postData: "redirect-body-secret", + }, + timestamp: 8, + }); + return finalUrl; +}; + +const emitMalformedRedirectPriorResponse = ( + socket: WebSocket, + command: FakeCdpCommand, + port: number, + initialUrl: string, +): void => { + const priorUrl = `http://127.0.0.1:${String(port)}/malformed-redirect-prior`; + event(socket, "Network.requestWillBeSent", command.sessionId, { + requestId: "request-1", + type: "Fetch", + request: { + url: priorUrl, + method: "POST", + headers: { "Content-Type": "application/json; charset=utf-8" }, + postData: JSON.stringify({ + operation: "preserve-prior", + token: "prior-body", + }), + }, + redirectResponse: { + url: initialUrl, + status: 307, + mimeType: "text/plain", + encodedDataLength: 23, + }, + timestamp: 8, + }); + event(socket, "Network.responseReceived", command.sessionId, { + requestId: "request-1", + response: { + url: priorUrl, + status: 201, + mimeType: "application/json", + headers: { + "Content-Length": "123", + "Content-Encoding": "gzip", + "Content-Security-Policy": "default-src 'self'", + "X-Model-Context": "prior-context-hint", + }, + }, + }); +}; + +const emitMalformedRedirect = ( + socket: WebSocket, + command: FakeCdpCommand, + port: number, + options: FakeOptions, +): string => { + const finalUrl = `http://127.0.0.1:${String(port)}/malformed-redirect-final`; + const redirectResponse = Object.hasOwn(options, "redirectResponseEnvelope") + ? options.redirectResponseEnvelope + : { + ...(options.redirectResponseUrl === undefined + ? {} + : { url: options.redirectResponseUrl }), + status: 302, + }; + event(socket, "Network.requestWillBeSent", command.sessionId, { + requestId: "request-1", + type: "Fetch", + request: { url: finalUrl, method: "GET" }, + redirectResponse, + timestamp: 9, + }); + return finalUrl; +}; + const emitNetworkEvents = ( socket: WebSocket, command: FakeCdpCommand, @@ -297,11 +424,15 @@ const emitNetworkEvents = ( }, }); const url = `http://127.0.0.1:${String(port)}/api?token=network-secret`; + const requestUrl = + options.redirectFromDisallowedOrigin === true + ? "https://private.example.test/start?token=initial-url-secret" + : url; event(socket, "Network.requestWillBeSent", command.sessionId, { requestId: "request-1", type: "Fetch", request: { - url, + url: requestUrl, method: "POST", headers: { Authorization: "Bearer request-secret", @@ -313,6 +444,7 @@ const emitNetworkEvents = ( filters: { active: true }, }), }, + timestamp: 7, initiator: { type: "script", stack: { @@ -326,6 +458,16 @@ const emitNetworkEvents = ( }, }, }); + if (options.malformedRedirectResponse === true) + emitMalformedRedirectPriorResponse(socket, command, port, url); + const responseUrl = + options.redirectFromDisallowedOrigin === true + ? emitReturnFromDisallowedRedirect(socket, command, port) + : options.malformedRedirectResponse === true + ? emitMalformedRedirect(socket, command, port, options) + : options.redirectWithinOrigin === true + ? emitSameOriginRedirectEvents(socket, command, port, url) + : url; if (options.redirectToDisallowedOrigin === true) event(socket, "Network.requestWillBeSent", command.sessionId, { requestId: "request-1", @@ -334,14 +476,22 @@ const emitNetworkEvents = ( url: "https://private.example.test/redirected", method: "GET", }, + redirectResponse: { + url, + status: 302, + mimeType: "text/plain", + encodedDataLength: 23, + headers: { "X-Private": "redirect-header-secret" }, + }, }); event(socket, "Network.responseReceived", command.sessionId, { requestId: "request-1", response: { url: - options.redirectToDisallowedOrigin === true + options.responseAfterMalformedUrl ?? + (options.redirectToDisallowedOrigin === true ? "https://private.example.test/redirected" - : url, + : responseUrl), status: 200, mimeType: "application/json", headers: { diff --git a/tests/fixtures/fakeCdpBrowserTypes.ts b/tests/fixtures/fakeCdpBrowserTypes.ts index 17d3452cc..27bab0dc3 100644 --- a/tests/fixtures/fakeCdpBrowserTypes.ts +++ b/tests/fixtures/fakeCdpBrowserTypes.ts @@ -75,6 +75,12 @@ export interface FakeOptions { readonly indexedDbDateKeys?: boolean; readonly foreignSessionEvents?: boolean; readonly redirectToDisallowedOrigin?: boolean; + readonly redirectFromDisallowedOrigin?: boolean; + readonly redirectWithinOrigin?: boolean; + readonly malformedRedirectResponse?: boolean; + readonly redirectResponseUrl?: string; + readonly redirectResponseEnvelope?: unknown; + readonly responseAfterMalformedUrl?: string; readonly unrelatedWorker?: boolean; readonly binaryWebSocketEvent?: boolean; readonly invalidBinaryWebSocketEvent?: boolean; diff --git a/tests/fixtures/fakeGhidra.mjs b/tests/fixtures/fakeGhidra.mjs index d0b9655f9..5cc0f2c16 100644 --- a/tests/fixtures/fakeGhidra.mjs +++ b/tests/fixtures/fakeGhidra.mjs @@ -108,11 +108,22 @@ const handleRequest = (socket, server, request, state) => { if (state.mode === "exit_tools" && request.method !== "shutdown") process.exit(74); if (state.mode === "hang_tools" && request.method !== "shutdown") return; - const inventory = inventoryResultFor(request.method); + const inventory = + request.method === "list_strings" && state.mode === "fragmented" + ? [stringItem("0x2000", "fixture 雪🦊")] + : request.method === "list_strings" && state.mode === "oversized_result" + ? [stringItem("0x2000", "x".repeat(64 * 1024 * 1024 + 1))] + : inventoryResultFor(request.method); if (inventory !== undefined) { - socket.write( - `${JSON.stringify({ id: request.id, ok: true, result: inventory })}\n`, - ); + const response = `${JSON.stringify({ id: request.id, ok: true, result: inventory })}\n`; + if (state.mode === "fragmented") { + const bytes = Buffer.from(response, "utf8"); + const unicodeOffset = bytes.indexOf(Buffer.from("雪", "utf8")); + const midpoint = + unicodeOffset >= 0 ? unicodeOffset + 1 : Math.floor(bytes.length / 2); + socket.write(bytes.subarray(0, midpoint)); + setTimeout(() => socket.write(bytes.subarray(midpoint)), 5); + } else socket.write(response); return; } if (request.method === "shutdown") { @@ -304,7 +315,7 @@ const stringItem = (address, value) => ({ string: { encoding: "UTF-8", termination: "present_or_not_required", - byte_length: value.length + 1, + byte_length: Buffer.byteLength(value, "utf8") + 1, }, }); diff --git a/tests/fixtures/fakeHopper.mjs b/tests/fixtures/fakeHopper.mjs index 07cc6bddd..a65073b35 100644 --- a/tests/fixtures/fakeHopper.mjs +++ b/tests/fixtures/fakeHopper.mjs @@ -135,7 +135,10 @@ const sendEchoWithProgress = (send, request) => { terminal: true, }, }); - send({ id: request.id, result: request.params ?? {} }); + send( + { id: request.id, result: request.params ?? {} }, + request.params?.fragmented === true, + ); }; if (typeof request.params?.gate === "string") heldReplies.set(request.id, reply); @@ -149,9 +152,12 @@ const server = createServer((socket) => { const send = (message, fragmented = false) => { const line = `${JSON.stringify(message)}\n`; if (!fragmented) return socket.write(line); - const midpoint = Math.floor(line.length / 2); - socket.write(line.slice(0, midpoint)); - setTimeout(() => socket.write(line.slice(midpoint)), 2); + const bytes = Buffer.from(line, "utf8"); + const unicodeOffset = bytes.indexOf(Buffer.from("雪", "utf8")); + const midpoint = + unicodeOffset >= 0 ? unicodeOffset + 1 : Math.floor(bytes.length / 2); + socket.write(bytes.subarray(0, midpoint)); + setTimeout(() => socket.write(bytes.subarray(midpoint)), 2); }; socket.on("data", (chunk) => { buffer += chunk; @@ -187,6 +193,9 @@ const server = createServer((socket) => { if (shutdownMode !== "cleanup-required") setTimeout(closeServer, 2); } else if (request.method === "hang") { // Deliberately leave the request pending. + } else if (request.method === "partial_then_wait") { + socket.write(`{"id":${request.id},"result":"stale`); + process.send?.({ id: request.id, method: "fixture_partial_write" }); } else if (request.method === "exit") { process.exit(7); } else if (request.method === "malformed") { diff --git a/tests/fixtures/filesystemSnapshotFifoProbe.mjs b/tests/fixtures/filesystemSnapshotFifoProbe.mjs new file mode 100644 index 000000000..de3e466b3 --- /dev/null +++ b/tests/fixtures/filesystemSnapshotFifoProbe.mjs @@ -0,0 +1,23 @@ +import { execFileSync } from "node:child_process"; +import { lstat, unlink, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; + +const [snapshotModulePath, directory] = process.argv.slice(2); +if (snapshotModulePath === undefined || directory === undefined) + throw new Error( + "Filesystem snapshot FIFO probe requires a module path and directory", + ); + +const { hashFile } = await import(pathToFileURL(snapshotModulePath)); +const path = join(directory, "observed-file"); +await writeFile(path, "regular file\n"); +const expected = await lstat(path); +await unlink(path); +execFileSync("mkfifo", [path]); +// This invokes the production lstat-to-open hashing seam with a known captured +// regular-file identity. The owner test bounds this child so an O_RDONLY FIFO +// regression cannot hang Vitest. +const digest = await hashFile(path, expected, 1_000); +if (digest !== null) throw new Error("Expected the FIFO to be rejected"); +process.stdout.write("rejected\n"); diff --git a/tests/fixtures/historicalHar.ts b/tests/fixtures/historicalHar.ts new file mode 100644 index 000000000..1c2542105 --- /dev/null +++ b/tests/fixtures/historicalHar.ts @@ -0,0 +1,36 @@ +/** Synthetic HAR 1.2 fixture with producer-declared sizes independent of Unicode content. */ +export const historicalHar = () => ({ + log: { + version: "1.2", + creator: { name: "rea-source-owned-fixture", version: "1" }, + entries: [ + { + startedDateTime: "2026-10-07T13:00:00.123+08:00", + time: 1.25, + request: { + method: "GET", + url: "https://example.test/a?token=ordinary#fragment", + httpVersion: "HTTP/2", + cookies: [], + headers: [], + queryString: [], + headersSize: -1, + bodySize: 0, + }, + response: { + status: 200, + statusText: "OK", + httpVersion: "HTTP/2", + cookies: [], + headers: [], + content: { size: 99, mimeType: "text/plain", text: "雪😀\r\n" }, + redirectURL: "", + headersSize: -1, + bodySize: 99, + }, + cache: {}, + timings: { send: 0, wait: 1.25, receive: 0 }, + }, + ], + }, +}); diff --git a/tests/fixtures/hopperCleanupTimeout.py b/tests/fixtures/hopperCleanupTimeout.py new file mode 100644 index 000000000..1a3d76c17 --- /dev/null +++ b/tests/fixtures/hopperCleanupTimeout.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +import importlib.util +import json +import subprocess +import sys +import time + +helper_path = sys.argv[1] +spec = importlib.util.spec_from_file_location("hopper_demo_x11", helper_path) +if spec is None or spec.loader is None: + raise SystemExit("unable to load helper") +helper = importlib.util.module_from_spec(spec) +spec.loader.exec_module(helper) + +child = subprocess.Popen( + [ + sys.executable, + "-c", + ( + "import signal,time;" + "signal.signal(signal.SIGTERM, signal.SIG_IGN);" + "print('ready', flush=True);" + "time.sleep(100)" + ), + ], + stdout=subprocess.PIPE, + text=True, +) +assert child.stdout is not None +if child.stdout.readline().strip() != "ready": + raise SystemExit("child did not become ready") + +started = time.monotonic() +helper.stop_process(child) +elapsed = time.monotonic() - started + +print( + json.dumps( + { + "returncode": child.returncode, + "elapsed_ms": round(elapsed * 1000), + }, + separators=(",", ":"), + ) +) + +if child.returncode != -9: + raise SystemExit(f"expected SIGKILL fallback, got {child.returncode}") +if not 1800 <= elapsed * 1000 < 4000: + raise SystemExit(f"unexpected cleanup duration: {elapsed}") diff --git a/tests/fixtures/hopperDialogDetection.py b/tests/fixtures/hopperDialogDetection.py new file mode 100644 index 000000000..b3a44ad3a --- /dev/null +++ b/tests/fixtures/hopperDialogDetection.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +import importlib.util +import json +import sys + +helper_path = sys.argv[1] +spec = importlib.util.spec_from_file_location("hopper_demo_x11", helper_path) +if spec is None or spec.loader is None: + raise SystemExit("unable to load helper") +helper = importlib.util.module_from_spec(spec) +spec.loader.exec_module(helper) + + +class FakeX11: + def __init__(self, titles, transients): + self.titles = titles + self.transients = transients + + def XFetchName(self, _display, window, target): + value = self.titles.get(window) + if value is None: + return 0 + target._obj.value = value.encode("utf-8") + return 1 + + def XGetTransientForHint(self, _display, window, target): + value = self.transients.get(window) + if value is None: + return 0 + target._obj.value = value + return 1 + + def XFree(self, _value): + return 1 + + +x11 = FakeX11( + { + 10: "Registration", + 11: "Hopper Disassembler", + 20: "Registration", + 21: "Unrelated application", + 30: "Other window", + }, + { + 10: 11, + 20: 21, + }, +) + +observed = [ + (20, 189, 370, 901, 284), + (30, 0, 0, 1280, 1024), + (10, 20, 100, 1200, 284), +] + +dialog = helper.find_demo_dialog(x11, 0, observed) +if dialog is None: + raise SystemExit("expected Hopper registration dialog was not detected") +if dialog[0] != 10: + raise SystemExit(f"wrong dialog detected: {dialog!r}") + +print( + json.dumps( + { + "dialog": dialog, + "click": helper.demo_click(dialog), + "wrong_parent_rejected": helper.find_demo_dialog( + x11, + 0, + [observed[0], observed[1]], + ) + is None, + }, + separators=(",", ":"), + ) +) diff --git a/tests/fixtures/processFidelity.mjs b/tests/fixtures/processFidelity.mjs index 83f130359..bac307179 100644 --- a/tests/fixtures/processFidelity.mjs +++ b/tests/fixtures/processFidelity.mjs @@ -1,4 +1,5 @@ import { spawn } from "node:child_process"; +import { writeFile } from "node:fs/promises"; const mode = process.argv[2]; @@ -63,6 +64,32 @@ if (mode === "interactive") { process.exit(23); } else if (mode === "hang") { setInterval(() => undefined, 1_000); +} else if (mode === "ready-hang") { + await writeFile(process.argv[3], "ready"); + process.stdout.write("ready\n"); + setInterval(() => undefined, 1_000); +} else if (mode === "detached-cleanup") { + const owned = spawn(process.execPath, [process.argv[1], "detached-child"], { + detached: true, + stdio: "ignore", + }); + const unowned = spawn(process.execPath, [process.argv[1], "detached-child"], { + detached: true, + env: { ...process.env, REA_PROCESS_RUN_ID: "unowned-neighbor" }, + stdio: "ignore", + }); + if (owned.pid === undefined || unowned.pid === undefined) + throw new Error("detached fixture children did not receive PIDs"); + owned.unref(); + unowned.unref(); + await writeFile( + process.argv[3], + JSON.stringify({ ownedPid: owned.pid, unownedPid: unowned.pid }), + ); + await new Promise((resolve) => setTimeout(resolve, 100)); + process.stdout.write("detached-ready\n"); +} else if (mode === "detached-child") { + setInterval(() => undefined, 1_000); } else { process.stderr.write("unknown fixture mode\n"); process.exit(2); diff --git a/tests/fixtures/processRunTokenParserProbe.swift b/tests/fixtures/processRunTokenParserProbe.swift new file mode 100644 index 000000000..b7185d433 --- /dev/null +++ b/tests/fixtures/processRunTokenParserProbe.swift @@ -0,0 +1,176 @@ +import Foundation + +func fixture( + arguments: [String], + environment: [String], + pointerSize: Int = 8, + executablePath: String = "/fixture/executable", + appleRecords: [String] = ["pfz=synthetic"], + zeroedPfz: Bool = false +) -> [UInt8] { + var bytes = withUnsafeBytes(of: Int32(arguments.count).littleEndian, Array.init) + let pathBytes = Array(executablePath.utf8) + bytes.append(contentsOf: pathBytes + [0]) + let prefixLength = Array("executable_path=".utf8).count + let padding = (pointerSize - ((prefixLength + pathBytes.count + 1) % pointerSize)) % pointerSize + bytes.append(contentsOf: repeatElement(UInt8(0), count: padding)) + for argument in arguments { bytes.append(contentsOf: Array(argument.utf8) + [0]) } + for entry in environment { bytes.append(contentsOf: Array(entry.utf8) + [0]) } + if zeroedPfz { + bytes.append(contentsOf: repeatElement(UInt8(0), count: Array("pfz=synthetic".utf8).count + 1)) + } + for record in appleRecords { bytes.append(contentsOf: Array(record.utf8) + [0]) } + return bytes +} + +@main +struct ProcessRunTokenParserProbe { + static func main() throws { + let argvDecoy = try token(in: fixture( + arguments: ["/fixture/executable", "REA_PROCESS_RUN_ID=argv-decoy", "trailing"], + environment: ["DECOY=words REA_PROCESS_RUN_ID=synthetic-decoy"] + )) + let realToken = try token(in: fixture( + arguments: ["/fixture/executable"], + environment: ["DECOY=words REA_PROCESS_RUN_ID=synthetic-decoy", "REA_PROCESS_RUN_ID=owned-token"] + )) + let emptyLaterArgument = try token(in: fixture( + arguments: ["/fixture/executable", "", "after-empty"], + environment: ["REA_PROCESS_RUN_ID=empty-argument-token"], + pointerSize: 8 + )) + let alternatePadding = try token(in: fixture( + arguments: ["/fixture/executable", "argument"], + environment: ["REA_PROCESS_RUN_ID=alternate-padding-token"], + pointerSize: 4 + ), pointerSize: 4) + let emptyArgv0Read = try token(in: fixture( + arguments: ["", "", "after-empty"], + environment: ["REA_PROCESS_RUN_ID=empty-argv0-token"], + pointerSize: 8 + )) + let finalAssignmentArgumentRead = try token(in: fixture( + arguments: ["/fixture/executable", "FOO=bar"], + environment: ["REA_PROCESS_RUN_ID=final-assignment-token"] + )) + let reservedArgumentIgnored = try token(in: fixture( + arguments: ["/fixture/executable", "REA_PROCESS_RUN_ID=argv-decoy"], + environment: ["DECOY=words REA_PROCESS_RUN_ID=synthetic-decoy"] + )) + let nonAsciiPathRead = try token(in: fixture( + arguments: ["/fixture/é", "FOO=bar"], + environment: ["REA_PROCESS_RUN_ID=utf8-path-token"], + pointerSize: 8, + executablePath: "/fixture/é" + )) + let appleVectorTokenFailsClosed: Bool + do { + _ = try token(in: fixture( + arguments: ["/fixture/executable"], + environment: ["pfz=user-value", "PATH=/usr/bin"], + appleRecords: ["pfz=synthetic", "REA_PROCESS_RUN_ID=apple-decoy"] + )) + appleVectorTokenFailsClosed = false + } catch RunTokenReadError.ambiguousEnvironmentBoundary { + appleVectorTokenFailsClosed = true + } + let clearedAppleVectorTokenFailsClosed: Bool + do { + _ = try token(in: fixture( + arguments: ["/fixture/executable"], + environment: ["REA_PROCESS_RUN_ID=cleared-pfz-owned-token"], + appleRecords: ["REA_PROCESS_RUN_ID=apple-decoy"], + zeroedPfz: true + )) + clearedAppleVectorTokenFailsClosed = false + } catch RunTokenReadError.ambiguousEnvironmentBoundary { + clearedAppleVectorTokenFailsClosed = true + } + let callerPfzBeforeTokenRead = try token(in: fixture( + arguments: ["/fixture/executable"], + environment: ["pfz=caller-value", "REA_PROCESS_RUN_ID=cleared-pfz-owned-token"], + zeroedPfz: true + )) + let emptyEnvironmentRecordsBeforeTokenFailClosed: Bool + do { + _ = try token(in: fixture( + arguments: ["/fixture/executable"], + environment: ["DECOY=value", "", "", "REA_PROCESS_RUN_ID=synthetic-decoy"], + appleRecords: [], + zeroedPfz: true + )) + emptyEnvironmentRecordsBeforeTokenFailClosed = false + } catch RunTokenReadError.ambiguousEnvironmentBoundary { + emptyEnvironmentRecordsBeforeTokenFailClosed = true + } + let zeroArgumentsRead = try token(in: fixture( + arguments: [], + environment: ["REA_PROCESS_RUN_ID=zero-argc-token"], + pointerSize: 4 + ), pointerSize: 4) + var truncatedArgv = withUnsafeBytes(of: Int32(2).littleEndian, Array.init) + let truncatedPath = Array("/fixture/executable".utf8) + truncatedArgv.append(contentsOf: truncatedPath + [0]) + let truncatedPadding = (8 - ((Array("executable_path=".utf8).count + truncatedPath.count + 1) % 8)) % 8 + truncatedArgv.append(contentsOf: repeatElement(UInt8(0), count: truncatedPadding)) + truncatedArgv.append(contentsOf: Array("/fixture/executable".utf8) + [0]) + truncatedArgv.append(contentsOf: Array("REA_PROCESS_RUN_ID=synthetic-decoy".utf8)) + let truncatedArgvFailsClosed: Bool + do { + _ = try token(in: truncatedArgv) + truncatedArgvFailsClosed = false + } catch { + truncatedArgvFailsClosed = true + } + let duplicateTokenFailsClosed: Bool + do { + _ = try token(in: fixture( + arguments: ["/fixture/executable"], + environment: ["REA_PROCESS_RUN_ID=first", "REA_PROCESS_RUN_ID=second"] + )) + duplicateTokenFailsClosed = false + } catch RunTokenReadError.duplicateToken { + duplicateTokenFailsClosed = true + } + let emptyEnvironmentFailsClosed: Bool + do { + _ = try token(in: fixture(arguments: ["/fixture/executable"], environment: [])) + emptyEnvironmentFailsClosed = false + } catch RunTokenReadError.environmentUnavailable { + emptyEnvironmentFailsClosed = true + } + let missingAppleBoundaryFailsClosed: Bool + do { + _ = try token(in: fixture( + arguments: ["/fixture/executable"], + environment: ["REA_PROCESS_RUN_ID=synthetic-decoy"], + appleRecords: [] + )) + missingAppleBoundaryFailsClosed = false + } catch RunTokenReadError.appleVectorUnavailable { + missingAppleBoundaryFailsClosed = true + } + + let result: [String: Any] = [ + "argvDecoyIgnored": argvDecoy == nil, + "realTokenRead": realToken == "owned-token", + "emptyLaterArgumentRead": emptyLaterArgument == "empty-argument-token", + "alternatePaddingRead": alternatePadding == "alternate-padding-token", + "truncatedArgvFailsClosed": truncatedArgvFailsClosed, + "duplicateTokenFailsClosed": duplicateTokenFailsClosed, + "emptyEnvironmentFailsClosed": emptyEnvironmentFailsClosed, + "missingAppleBoundaryFailsClosed": missingAppleBoundaryFailsClosed, + "emptyArgv0Read": emptyArgv0Read == "empty-argv0-token", + "finalAssignmentArgumentRead": finalAssignmentArgumentRead == "final-assignment-token", + "reservedArgumentIgnored": reservedArgumentIgnored == nil, + "nonAsciiPathRead": nonAsciiPathRead == "utf8-path-token", + "appleVectorTokenFailsClosed": appleVectorTokenFailsClosed, + "clearedAppleVectorTokenFailsClosed": clearedAppleVectorTokenFailsClosed, + "callerPfzBeforeTokenRead": callerPfzBeforeTokenRead == "cleared-pfz-owned-token", + "emptyEnvironmentRecordsBeforeTokenFailClosed": emptyEnvironmentRecordsBeforeTokenFailClosed, + "zeroArgumentsRead": zeroArgumentsRead == "zero-argc-token", + ] + let output = try JSONSerialization.data(withJSONObject: result, options: [.sortedKeys]) + FileHandle.standardOutput.write(output) + } +} diff --git a/tests/fixtures/processRunTokenSnapshotProbe.swift b/tests/fixtures/processRunTokenSnapshotProbe.swift new file mode 100644 index 000000000..bfb2aae7c --- /dev/null +++ b/tests/fixtures/processRunTokenSnapshotProbe.swift @@ -0,0 +1,40 @@ +import Darwin +import Foundation + +@main +struct ProcessRunTokenSnapshotProbe { + static func main() throws { + var sizeQueries = 0 + var readAttempts = 0 + let retried = readProcessSnapshot { _, output, size in + guard output != nil else { + sizeQueries += 1 + size = MemoryLayout.stride + return 0 + } + readAttempts += 1 + if readAttempts == 1 { + errno = ENOMEM + return -1 + } + size = 0 + return 0 + } + + let otherError = readProcessSnapshot { _, output, size in + if output == nil { + size = MemoryLayout.stride + return 0 + } + errno = EACCES + return -1 + } + + let output: [String: Any] = [ + "retriedAfterFreshSize": retried.failure == nil && sizeQueries == 2 && readAttempts == 2, + "preservedOtherErrno": otherError.failure == "process_table_failed_\(EACCES)" + ] + let data = try JSONSerialization.data(withJSONObject: output, options: [.sortedKeys]) + FileHandle.standardOutput.write(data) + } +} diff --git a/tests/fixtures/processSnapshotCancellation.mjs b/tests/fixtures/processSnapshotCancellation.mjs new file mode 100644 index 000000000..6da752d82 --- /dev/null +++ b/tests/fixtures/processSnapshotCancellation.mjs @@ -0,0 +1,8 @@ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +const [root] = process.argv.slice(2); +if (root === undefined) + throw new Error("Snapshot cancellation fixture requires an observation root"); + +await writeFile(join(root, "created-after-start"), "x"); diff --git a/tests/process-global/cliJsonInputStatus.test.ts b/tests/process-global/cliJsonInputStatus.test.ts index 582bb5946..d69dcbc61 100644 --- a/tests/process-global/cliJsonInputStatus.test.ts +++ b/tests/process-global/cliJsonInputStatus.test.ts @@ -11,43 +11,31 @@ afterEach(() => { }); describe("CLI JSON input logging status", () => { - it.each([ - {}, - { input_path: "input.json", input_reason: "invalid-json" }, - { input_path: "missing.json", input_reason: "read-failed" }, - { input_path: "", input_reason: "read-failed" }, - ])( - "preserves failure diagnostics and logs an error for %j", - async (metadata) => { - const parsed = await parseCliJsonInput("{", "test-input"); - if (parsed.ok) throw new Error("Expected malformed inline JSON to fail"); - const value = Object.assign({}, parsed.error, metadata); - const serialized = JSON.stringify(value); - const lines: string[] = []; - const logger = pino( - { level: "info" }, - { write: (line) => lines.push(line) }, - ); - process.exitCode = undefined; + it("preserves parsed failure diagnostics and logs an error", async () => { + const parsed = await parseCliJsonInput("{", "test-input"); + if (parsed.ok) throw new Error("Expected malformed inline JSON to fail"); + const value = parsed.error; + const serialized = JSON.stringify(value); + const lines: string[] = []; + const logger = pino( + { level: "info" }, + { write: (line) => lines.push(line) }, + ); + process.exitCode = undefined; - const output = await logCliCommand( - logger, - "test-input", - async () => value, - ); + const output = await logCliCommand(logger, "test-input", async () => value); - expect(output).toBe(value); - expect(JSON.stringify(output)).toBe(serialized); - expect(process.exitCode).toBe(1); - expect(lines).toHaveLength(1); - expect(JSON.parse(lines.join(""))).toMatchObject({ - level: 50, - command: "test-input", - status: "error", - msg: "CLI command failed", - }); - }, - ); + expect(output).toBe(value); + expect(JSON.stringify(output)).toBe(serialized); + expect(process.exitCode).toBe(1); + expect(lines).toHaveLength(1); + expect(JSON.parse(lines.join(""))).toMatchObject({ + level: 50, + command: "test-input", + status: "error", + msg: "CLI command failed", + }); + }); it("leaves ordinary parsed JSON successful and unchanged", async () => { const parsed = await parseCliJsonInput( diff --git a/tests/process-global/verifierRun.test.ts b/tests/process-global/verifierRun.test.ts index 3b42e282d..10cb80f64 100644 --- a/tests/process-global/verifierRun.test.ts +++ b/tests/process-global/verifierRun.test.ts @@ -104,26 +104,6 @@ describe("verifier run identity", { concurrent: false }, () => { } }, ); - - it.runIf(process.platform !== "win32")( - "bounds cleanup when a verifier child ignores SIGTERM", - async () => { - const child = spawn( - process.execPath, - [ - "-e", - "process.on('SIGTERM', () => {}); process.stdout.write('ready'); setInterval(() => {}, 1000)", - ], - { stdio: ["ignore", "pipe", "ignore"] }, - ); - try { - await childReady(child); - } finally { - await stopChild(child); - } - expect(child.signalCode).toBe("SIGKILL"); - }, - ); }); const spawnReadyChild = (env: NodeJS.ProcessEnv): ChildProcess => diff --git a/tests/process-global/vitestConfiguration.test.ts b/tests/process-global/vitestConfiguration.test.ts deleted file mode 100644 index 33dee06ff..000000000 --- a/tests/process-global/vitestConfiguration.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { execFile } from "node:child_process"; -import { readdir } from "node:fs/promises"; -import { join, relative, resolve } from "node:path"; -import { promisify } from "node:util"; - -import { describe, expect, it } from "vitest"; - -const execute = promisify(execFile); -describe("Vitest project configuration", () => { - it("classifies every deterministic test in exactly one project", async () => { - const { stdout } = await execute( - process.execPath, - [ - resolve("node_modules/vitest/vitest.mjs"), - "list", - "--filesOnly", - "--staticParse", - ], - { cwd: process.cwd(), maxBuffer: 4 * 1_024 * 1_024 }, - ); - const classified = parseProjects(stdout); - const repositoryTests = [ - ...(await testFiles("src")), - ...(await testFiles("tests")), - ].sort(); - - expect( - [...classified.values()] - .filter((owners) => owners.length > 1) - .map((owners) => owners.join(", ")), - ).toEqual([]); - expect([...classified.keys()].sort()).toEqual(repositoryTests); - }, 20_000); -}); - -const parseProjects = (output: string): Map => { - const classified = new Map(); - for (const line of output.split("\n")) { - const trimmed = line.trim(); - const separator = trimmed.indexOf("] "); - if (!trimmed.startsWith("[") || separator < 2) continue; - const project = trimmed.slice(1, separator); - const path = trimmed.slice(separator + 2); - if (path.length === 0) continue; - const owners = classified.get(path) ?? []; - owners.push(project); - classified.set(path, owners); - } - return classified; -}; - -const testFiles = async (root: string): Promise => { - const files: string[] = []; - for (const entry of await readdir(root, { withFileTypes: true })) { - const path = join(root, entry.name); - if (entry.isDirectory()) files.push(...(await testFiles(path))); - else if (entry.isFile() && entry.name.endsWith(".test.ts")) - files.push(relative(process.cwd(), path)); - } - return files; -}; diff --git a/third_party/web-network-captures/README.md b/third_party/web-network-captures/README.md new file mode 100644 index 000000000..49d0bc110 --- /dev/null +++ b/third_party/web-network-captures/README.md @@ -0,0 +1,36 @@ +# Historical network upstream profiles + +REA uses unmodified upstream packages or a caller-supplied tool. The adapter +code lives under `src/browser/history/` and `bridge/mitmproxy/`; this directory +records provenance rather than vendoring a second parser implementation. + +| Upstream | Fixed profile | Use | +| --- | --- | --- | +| [har-schema](https://github.com/ahmadnassri/har-schema) | npm 2.0.0, ISC | HAR 1.2 draft-06 schemas | +| [jsonc-parser](https://github.com/microsoft/node-jsonc-parser) | npm 3.3.1, MIT | Strict JSON visitor; member sequence, duplicate detection and prototype-safe materialization | +| [lossless-json](https://github.com/josdejong/lossless-json) | npm 4.3.1 | Original JSON numeric lexemes and safe-number classification | +| [AJV](https://github.com/ajv-validator/ajv) | npm 8.20.0 | Upstream schema validation | +| [ajv-formats](https://github.com/ajv-validator/ajv-formats) | npm 3.0.1 | Upstream URI, timestamp and address formats | +| [mitmproxy](https://github.com/mitmproxy/mitmproxy) | v12.2.3, commit `6c09d56e4c29a92f5ad01b03199977584b8ea14f`, MIT | Unchanged native `mitmproxy.io.tnetstring.load`; upstream `FlowWriter` / `SaveHar` for ephemeral verification fixtures | + +The npm lockfile pins exact package versions and distribution integrity hashes; +their original code and license files remain in the installed packages. Native +mitmproxy is bring-your-own. REA never installs or upgrades it and rejects a +different runtime version. The Linux verification lane downloads the official +standalone release into runner temporary storage with fixed archive SHA-256 +`2e95286b618fa6fd33e5e62a78c2e5112571d85f42ec2bac29b97ee242bdb5c5`. +This is a pinned archive digest, not a publisher signature claim. + +No native captures or game/application binaries are checked in. Verification +uses source-owned Python fixtures and upstream writers. The original file +digest/record representation is preserved independently of the decoded view; +upstream conversion defaults are not imported into native states. + +The observed `SaveHar` 12.2.3 optional `postData.text: null` quirk is admitted +only in a validation copy for that declared producer profile, with the original +null retained. The upstream schemas themselves remain unchanged. + +The strict JSON visitor supplies member events before object materialization; +REA rejects all duplicate names and retains original numeric token spelling. +The shared JSON schema currently drops `__proto__` members, so both adapters +report that representation as unsupported instead of returning incomplete data. diff --git a/turbo.json b/turbo.json index 62e4f0ade..5ab13eb44 100644 --- a/turbo.json +++ b/turbo.json @@ -8,18 +8,25 @@ "package-lock.json", "scripts/check-dependency-install.mjs", "scripts/clean-build-output.mjs", + "scripts/generate-mcp-tool-catalog.mjs", "scripts/generate-package-metadata.mjs", "scripts/lib/generated-file.mjs", "skills/reverse-engineer-anything/SKILL.md", "src/**/*.ts", "!src/**/*.test.ts", "!src/**/*.fixture.ts", + "!src/generatedMcpToolCatalog.ts", "tsconfig.json", "tsconfig.build.json" ], - "outputs": ["dist/**", ".cache/tsconfig.build.tsbuildinfo"] + "outputs": [ + "dist/**", + ".cache/tsconfig.build.tsbuildinfo", + "src/generatedMcpToolCatalog.ts" + ] }, "typecheck": { + "dependsOn": ["build"], "inputs": [ ".nvmrc", "package.json", @@ -57,6 +64,7 @@ }, "format:check": {}, "knip": { + "dependsOn": ["build"], "inputs": [ ".nvmrc", "knip.json", diff --git a/website/AGENTS.md b/website/AGENTS.md new file mode 100644 index 000000000..a4bf9b8dc --- /dev/null +++ b/website/AGENTS.md @@ -0,0 +1,23 @@ +# Website maintenance + +> A good website is like a good paper: easy to follow, clear and concise, with a clean, refined presentation. +> +> — N0zoM1z0 + +Read [README.md](README.md) and [style-guide.md](style-guide.md) before changing +website content, layout or figures. Use [figures.md](figures.md) and `evidence/` +for asset notes and the source of case-study claims. + +- Write English, direct headings and concrete instructions. Each paragraph + should explain a new fact or action. +- Reuse the shared styles, prompts, figures and code-step components. Keep a + case study focused on one question, with further examples in native details. +- Show what REA returns and how the agent uses it. Preserve source attribution, + target identity and verification scope. +- Keep personal paths, account data, credentials and raw captures outside the + public assets. Use generic inputs and label shortened display paths. +- Check both local root and `/rea/` paths, mobile layouts, expanded details, + copying and downloads. Run `scripts/prepare-website.py` followed by + `scripts/verify-website.py`. +- Keep `website-pages.yml` as the sole, manual Pages publisher. Follow the + publication procedure in the website README. diff --git a/website/README.md b/website/README.md index 515e77238..abbce8908 100644 --- a/website/README.md +++ b/website/README.md @@ -1,10 +1,17 @@ # REA website +> A good website is like a good paper: easy to follow, clear and concise, with a clean, refined presentation. +> +> — N0zoM1z0 + An English static website with explanatory figures, worked guides and DX-Ball, Notion and TH04 investigations. The public files are in `website/public/`. The site uses HTML, CSS and a small script for copying code and following the assembly-to-C comparison. Python packages the downloadable example; there is no frontend bundler or npm dependency. +[style-guide.md](style-guide.md) explains the writing, page structure, figures, +visual system and review process. Read it before adding or revising a page. + ## Local preview From the repository root: @@ -57,6 +64,11 @@ respecting reduced-motion preferences. Both the prompt and animation work without JavaScript. The homepage and agent setup section share a copyable installation prompt; setup still presents its plan for approval. +Reading pages share a small `↑ Top` link at the bottom right. It appears after +scrolling and returns to the page header, with smooth scrolling when reduced +motion is disabled. Keyboard activation returns focus to the first navigation +link. Without JavaScript, the link stays visible and uses its `#top` anchor. + DX-Ball figures and findings refer to the linked 7 October 2026 checkpoint, commit `a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4`. Update those links and figures together when moving to another checkpoint. Case-study source excerpts come @@ -78,7 +90,8 @@ code; the scoped lint override admits `require` only in that example directory. The Notion case follows the same HTML figure style. Its short excerpts explain the packaged clipboard bridge; separate web-cache probes illustrate the rich -clipboard and Markdown formats. Only selected source details and generic +clipboard format, with Markdown tables available as an additional example in +collapsed details. Only selected source details and generic example data belong on the site. Machine paths, account identifiers, local configuration, complete vendor bundles and raw captured results stay outside the website. diff --git a/website/evidence/guide-examples.md b/website/evidence/guide-examples.md index 70cdc859c..476c0fbd0 100644 --- a/website/evidence/guide-examples.md +++ b/website/evidence/guide-examples.md @@ -71,6 +71,10 @@ Full local outputs and the browser verification harness are under ## Example source digests +These digests identify the files used for the recorded analyses. The browser +page later gained the site's shared `↑ Top` navigation; its `export.js` and +`notes.json` retain the versions below. + | File | SHA-256 | | -------------------------------------- | ------------------------------------------------------------------ | | `examples/notes-electron/csv.js` | `6875aeecc1d3e6c1ff5b888a199c26b377d812d5b10e5fb4025e192a206b0018` | diff --git a/website/figures.md b/website/figures.md index ddd233677..525c3df1c 100644 --- a/website/figures.md +++ b/website/figures.md @@ -1,5 +1,13 @@ # REA website figures +> A good website is like a good paper: easy to follow, clear and concise, with a clean, refined presentation. +> +> — N0zoM1z0 + +Use [style-guide.md](style-guide.md#make-figures-explain-a-relationship) when adding +or revising a figure. The prompts below are historical layout references; use +the current SVG source for published labels. + The published diagrams are SVG source assets: - `public/assets/figures/rea-investigation-flow.svg` — 1774 × 887 viewBox. diff --git a/website/public/assets/figures/dx-ball-sound-pan-investigation.svg b/website/public/assets/figures/dx-ball-sound-pan-investigation.svg index 103bd751b..b1a2a51bc 100644 --- a/website/public/assets/figures/dx-ball-sound-pan-investigation.svg +++ b/website/public/assets/figures/dx-ball-sound-pan-investigation.svg @@ -36,11 +36,11 @@ Maintained C Brick-hit caller 20 + 30 × tile_x - Original-x86 + Original code comparison 3,205 cases - VC4.0 compiler - replay + VC4.0 build + comparison 63 matching bytes diff --git a/website/public/assets/main.js b/website/public/assets/main.js index 40a1d9808..1cdb38e0e 100644 --- a/website/public/assets/main.js +++ b/website/public/assets/main.js @@ -60,3 +60,27 @@ function initializeStepComparisons() { } initializeStepComparisons(); + +function initializeBackToTop() { + const link = document.querySelector(".back-to-top"); + if (link === null) return; + + const updateVisibility = () => { + link.hidden = window.scrollY < 400; + }; + window.addEventListener("scroll", updateVisibility, { passive: true }); + updateVisibility(); + + link.addEventListener("click", (event) => { + event.preventDefault(); + const reduceMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches; + window.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" }); + document + .querySelector(".brand, .breadcrumb a") + ?.focus({ preventScroll: true }); + }); +} + +initializeBackToTop(); diff --git a/website/public/assets/styles.css b/website/public/assets/styles.css index ce65e9ad3..d1f36e0ee 100644 --- a/website/public/assets/styles.css +++ b/website/public/assets/styles.css @@ -99,6 +99,31 @@ section[id] { .skip-link:focus { top: 0.75rem; } +.back-to-top { + position: fixed; + right: calc(1.5rem + env(safe-area-inset-right, 0px)); + bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px)); + z-index: 5; + display: inline-flex; + align-items: center; + gap: 0.4rem; + min-height: 44px; + padding: 0.5rem 0.85rem; + border: 1px solid var(--line); + border-radius: 6px; + background: var(--page); + color: var(--muted); + font-size: 0.875rem; + font-weight: 600; + text-decoration: none; +} +.back-to-top[hidden] { + display: none; +} +.back-to-top:hover { + background: var(--soft); + color: var(--ink); +} .site-header { border-bottom: 1px solid var(--line); } @@ -689,7 +714,7 @@ pre code { } .provider { display: grid; - grid-template-columns: 105px 1fr; + grid-template-columns: 105px minmax(0, 1fr); gap: 1.3rem; padding-block: 1.2rem; border-top: 1px solid var(--line); @@ -1023,6 +1048,10 @@ pre code { } @media (max-width: 640px) { + .back-to-top { + right: calc(1rem + env(safe-area-inset-right, 0px)); + bottom: calc(1rem + env(safe-area-inset-bottom, 0px)); + } .container { width: calc(100% - 2.5rem); } @@ -1107,7 +1136,7 @@ pre code { gap: 1rem; } .provider { - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr); gap: 0.25rem; } .guide-entry { diff --git a/website/public/examples/notes-web/index.html b/website/public/examples/notes-web/index.html index 00d2c80b8..7f420d16d 100644 --- a/website/public/examples/notes-web/index.html +++ b/website/public/examples/notes-web/index.html @@ -7,8 +7,9 @@ + - +
+ + Top + diff --git a/website/public/get-started/index.html b/website/public/get-started/index.html index 15314573f..be8b31d02 100644 --- a/website/public/get-started/index.html +++ b/website/public/get-started/index.html @@ -13,7 +13,7 @@ - +