[GH-ISSUE #6034] [Bug] Artifact inventory views flatten parent schema issues into a source message #3680

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

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

inspect-analysis-view flattens parent schema failures for both inventory_artifact and inspect_artifact into one canonical issue at ["source"] with reason invalid_value. The owning schema's field paths, reasons and expected type survive only inside a serialized Zod issue list in message. The production application error also has no retained cause.

An inventory with only manifest.node_count changed to a string and an inspection with only coverage.substeps_completed changed to a string both reproduce this. The corresponding valid payloads return summary Evidence successfully.

Reproduction

From the built source checkout, create and inspect a one-entry ZIP:

python3 - <<'PYTHON'
import zipfile
with zipfile.ZipFile('fixture.zip', 'w') as archive:
    archive.writestr('main.js', 'console.log(1);\n')
PYTHON
node scripts/rea.mjs inspect-artifact fixture.zip --json > inspection.json

Use the real inspection and its nested inventory to create digest-valid inline requests. Malformed results below are deliberately constructed; they were not emitted by inspect-artifact.

node --input-type=module <<'JS'
import {readFileSync, writeFileSync} from 'node:fs';
import {spawnSync} from 'node:child_process';
import {createEvidence} from './dist/domain/evidence.js';
import {artifactInventoryResultSchema} from './dist/domain/artifactGraph.js';
import {artifactInspectionResultSchema} from './dist/domain/artifactInspection.js';
import {inspectAnalysisView} from './dist/application/analysisView/AnalysisViewService.js';

const inspection = JSON.parse(readFileSync('inspection.json', 'utf8'));
const inventory = inspection.normalized_result.substeps[0].evidence;
for (const [name, carrier, schema, field] of [
  ['inventory', inventory, artifactInventoryResultSchema, ['manifest', 'node_count']],
  ['inspection', inspection, artifactInspectionResultSchema,
    ['coverage', 'substeps_completed']]
]) {
  for (const variant of ['valid', 'type', 'missing', 'page']) {
    const result = structuredClone(carrier.normalized_result);
    if (variant === 'missing') delete result[field[0]][field[1]];
    else if (variant !== 'valid') result[field[0]][field[1]] = 'bad';
    const subject = carrier.subject;
    const evidence = createEvidence({
      path: subject.local_path, sha256: subject.digest.sha256,
      format: subject.format, architecture: subject.architecture
    }, carrier.provider, {operation: carrier.operation, parameters: {}, result});
    const request = {
      source: {kind: 'inline', evidence},
      view: variant === 'page'
        ? {kind: 'inventory', collection: 'nodes', offset: 0, limit: 1}
        : {kind: 'summary'}
    };
    const parsed = schema.safeParse(result);
    console.log(name, variant, parsed.success ? 'schema valid' : parsed.error.issues);
    const file = `${name}-${variant}.json`;
    writeFileSync(file, JSON.stringify(request));
    const reply = spawnSync(process.execPath,
      ['scripts/rea.mjs', 'inspect-analysis-view', file, '--json'], {encoding: 'utf8'});
    console.log('exit', reply.status, 'stderr', reply.stderr, 'stdout', reply.stdout);
    const application = inspectAnalysisView(request);
    if (!application.ok) console.log('cause', application.error.cause);
  }
}
JS

Executed once per case: both valid summaries exit 0; all six malformed requests exit 1; stderr is empty throughout. The application call returns cause: undefined for the malformed cases. No external engine or simulated provider is involved.

Expected behavior

Keep rejecting malformed parent results, but preserve one structured issue per owning-schema failure under ["source", "normalized_result", ...fieldPath]. For a string count, report invalid_type and the expected numeric type; distinguish a missing count through the existing issue projection. Retain the original parse error as the application error's cause.

This matches the explicit shared parent parser contract and the native parent field-path regression. These are logical correction paths; the existing convention omits the inline transport's Evidence wrapper.

Impact and scope

P3: inconsistent correction metadata for rejected parent data. Validation still fails closed and the tested valid payloads succeed. The message remains readable and includes the original field information, so callers can inspect its text or validate with the owning schema. No incorrect success, data loss, valid-input analysis failure or downstream automated recovery failure was observed. Frequency in normal use is unknown.

Executed scope covers top-level schema failures for both parent operations, using summary and inventory/nodes views. Nested inventory-schema failures and the MCP transport were not separately exercised. Subject identity and other deliberate integrity TypeError failures are outside this report.

REA version

rea-agents 6.7.0; source revision 5241597594.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; existing built runtime and locked dependencies.

Target and artifact context

One-entry ZIP containing a JavaScript file. Real inspection and nested atomic inventory Evidence supply the valid controls; malformed variants change or omit one numeric field and are resealed with createEvidence before submission.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

For the wrong-typed inventory count, direct owning-schema validation produces exactly one issue:

{"expected":"number","code":"invalid_type","path":["manifest","node_count"],"message":"Invalid input: expected number, received string"}

The CLI instead returns these relevant fields:

{
  "code": "invalid_request",
  "category": "invalid_input",
  "retryable": false,
  "recovery": {
    "kind": "inspect_input_contract",
    "operation": "inspect_analysis_view"
  },
  "details": {
    "operation": "inspect_analysis_view",
    "issues": [
      {
        "path": [
          "source"
        ],
        "reason": "invalid_value",
        "message": "[\n  {\n    \"expected\": \"number\",\n    \"code\": \"invalid_type\",\n    \"path\": [\n      \"manifest\",\n      \"node_count\"\n    ],\n    \"message\": \"Invalid input: expected number, received string\"\n  }\n]"
      }
    ]
  }
}

For the inspection case, the owning path is ["coverage","substeps_completed"]; the public path and reason are again ["source"] and invalid_value. Missing-field cases retain received undefined only inside the string message. Inventory/nodes selection produces the same failure as summary selection.

Typed outcome or recovery expected

Keep invalid_request / invalid_input, retryable:false and inspect_input_contract recovery for inspect_analysis_view. Preserve the safe field-level correction data and original cause without changing Evidence authentication or accepting malformed results.

Analysis or suspected cause

Source-derived: the owning inventory and inspection parsers use Zod parsing; the atomic inventory parser does likewise. The view's sourceFailure treats ZodError like an authored TypeError message and passes only cause.message into the fixed-path helper. This loses the canonical issue structure and cause before CLI serialization.

Additional context

Related but separate: #6001 concerns the incompatible-view selection branch; correcting that branch's path does not change these malformed-parent requests. #6003 concerns binary-layout parent parsing, which uses a separate branch. This inventory schema-error report is also separate from pcap and web-network parent parsing.

Fetched upstream main 6d356b8434 retains the relevant sourceFailure, fixed-path helper and owning schema parses unchanged. Its changes to this view concern traversal-coverage output. That later source was inspected, not executed. MCP binds the same application service by source inspection, but this reproduction exercises CLI and the application boundary only.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6034 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `inspect-analysis-view` flattens parent schema failures for both `inventory_artifact` and `inspect_artifact` into one canonical issue at `["source"]` with reason `invalid_value`. The owning schema's field paths, reasons and expected type survive only inside a serialized Zod issue list in `message`. The production application error also has no retained cause. An inventory with only `manifest.node_count` changed to a string and an inspection with only `coverage.substeps_completed` changed to a string both reproduce this. The corresponding valid payloads return summary Evidence successfully. ### Reproduction From the built source checkout, create and inspect a one-entry ZIP: ```sh python3 - <<'PYTHON' import zipfile with zipfile.ZipFile('fixture.zip', 'w') as archive: archive.writestr('main.js', 'console.log(1);\n') PYTHON node scripts/rea.mjs inspect-artifact fixture.zip --json > inspection.json ``` Use the real inspection and its nested inventory to create digest-valid inline requests. Malformed results below are deliberately constructed; they were not emitted by `inspect-artifact`. ```sh node --input-type=module <<'JS' import {readFileSync, writeFileSync} from 'node:fs'; import {spawnSync} from 'node:child_process'; import {createEvidence} from './dist/domain/evidence.js'; import {artifactInventoryResultSchema} from './dist/domain/artifactGraph.js'; import {artifactInspectionResultSchema} from './dist/domain/artifactInspection.js'; import {inspectAnalysisView} from './dist/application/analysisView/AnalysisViewService.js'; const inspection = JSON.parse(readFileSync('inspection.json', 'utf8')); const inventory = inspection.normalized_result.substeps[0].evidence; for (const [name, carrier, schema, field] of [ ['inventory', inventory, artifactInventoryResultSchema, ['manifest', 'node_count']], ['inspection', inspection, artifactInspectionResultSchema, ['coverage', 'substeps_completed']] ]) { for (const variant of ['valid', 'type', 'missing', 'page']) { const result = structuredClone(carrier.normalized_result); if (variant === 'missing') delete result[field[0]][field[1]]; else if (variant !== 'valid') result[field[0]][field[1]] = 'bad'; const subject = carrier.subject; const evidence = createEvidence({ path: subject.local_path, sha256: subject.digest.sha256, format: subject.format, architecture: subject.architecture }, carrier.provider, {operation: carrier.operation, parameters: {}, result}); const request = { source: {kind: 'inline', evidence}, view: variant === 'page' ? {kind: 'inventory', collection: 'nodes', offset: 0, limit: 1} : {kind: 'summary'} }; const parsed = schema.safeParse(result); console.log(name, variant, parsed.success ? 'schema valid' : parsed.error.issues); const file = `${name}-${variant}.json`; writeFileSync(file, JSON.stringify(request)); const reply = spawnSync(process.execPath, ['scripts/rea.mjs', 'inspect-analysis-view', file, '--json'], {encoding: 'utf8'}); console.log('exit', reply.status, 'stderr', reply.stderr, 'stdout', reply.stdout); const application = inspectAnalysisView(request); if (!application.ok) console.log('cause', application.error.cause); } } JS ``` Executed once per case: both valid summaries exit 0; all six malformed requests exit 1; stderr is empty throughout. The application call returns `cause: undefined` for the malformed cases. No external engine or simulated provider is involved. ### Expected behavior Keep rejecting malformed parent results, but preserve one structured issue per owning-schema failure under `["source", "normalized_result", ...fieldPath]`. For a string count, report `invalid_type` and the expected numeric type; distinguish a missing count through the existing issue projection. Retain the original parse error as the application error's cause. This matches the explicit [shared parent parser contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/analysisViewParent.ts#L7-L25) and the [native parent field-path regression](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/analysisView.test.ts#L265-L287). These are logical correction paths; the existing convention omits the inline transport's Evidence wrapper. ### Impact and scope P3: inconsistent correction metadata for rejected parent data. Validation still fails closed and the tested valid payloads succeed. The message remains readable and includes the original field information, so callers can inspect its text or validate with the owning schema. No incorrect success, data loss, valid-input analysis failure or downstream automated recovery failure was observed. Frequency in normal use is unknown. Executed scope covers top-level schema failures for both parent operations, using summary and inventory/nodes views. Nested inventory-schema failures and the MCP transport were not separately exercised. Subject identity and other deliberate integrity `TypeError` failures are outside this report. ### REA version rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; existing built runtime and locked dependencies. ### Target and artifact context One-entry ZIP containing a JavaScript file. Real inspection and nested atomic inventory Evidence supply the valid controls; malformed variants change or omit one numeric field and are resealed with `createEvidence` before submission. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence For the wrong-typed inventory count, direct owning-schema validation produces exactly one issue: ```json {"expected":"number","code":"invalid_type","path":["manifest","node_count"],"message":"Invalid input: expected number, received string"} ``` The CLI instead returns these relevant fields: ```json { "code": "invalid_request", "category": "invalid_input", "retryable": false, "recovery": { "kind": "inspect_input_contract", "operation": "inspect_analysis_view" }, "details": { "operation": "inspect_analysis_view", "issues": [ { "path": [ "source" ], "reason": "invalid_value", "message": "[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"manifest\",\n \"node_count\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n }\n]" } ] } } ``` For the inspection case, the owning path is `["coverage","substeps_completed"]`; the public path and reason are again `["source"]` and `invalid_value`. Missing-field cases retain `received undefined` only inside the string message. Inventory/nodes selection produces the same failure as summary selection. ### Typed outcome or recovery expected Keep `invalid_request` / `invalid_input`, `retryable:false` and `inspect_input_contract` recovery for `inspect_analysis_view`. Preserve the safe field-level correction data and original cause without changing Evidence authentication or accepting malformed results. ### Analysis or suspected cause Source-derived: [the owning inventory and inspection parsers](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/artifactInventoryEvidence.ts#L100-L117) use Zod parsing; the [atomic inventory parser](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/artifactInventoryEvidence.ts#L184-L198) does likewise. [The view's `sourceFailure`](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/artifactInventoryView.ts#L188-L219) treats `ZodError` like an authored `TypeError` message and passes only `cause.message` into [the fixed-path helper](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/artifactInventoryView.ts#L124-L131). This loses the canonical issue structure and cause before CLI serialization. ### Additional context Related but separate: [#6001](https://github.com/morluto/rea/issues/6001) concerns the incompatible-view selection branch; correcting that branch's path does not change these malformed-parent requests. [#6003](https://github.com/morluto/rea/issues/6003) concerns binary-layout parent parsing, which uses a separate branch. This inventory schema-error report is also separate from pcap and web-network parent parsing. Fetched upstream main 6d356b84344659085f2b912857b5e27fe0160c9b retains the relevant `sourceFailure`, fixed-path helper and owning schema parses unchanged. Its changes to this view concern traversal-coverage output. That later source was inspected, not executed. MCP binds the same application service by source inspection, but this reproduction exercises CLI and the application boundary only.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3680
No description provided.