[GH-ISSUE #3354] [Bug] Reconstruction coverage reports ready with a missing transitive dependency #1223

Closed
opened 2026-10-16 21:02:59 +00:00 by bsakaguchi · 2 comments
Owner

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

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

evaluate-reconstruction-coverage reports ready when a required surface depends on an inventoried surface whose own declared dependency is missing. The same missing dependency produces partial when it is listed directly on the required surface or when the intermediate surface is also explicitly required. A caller can therefore receive a clean closure result for a dependency graph that still has a known inventory gap.

All four reproduced CLI commands exited zero. The complete base case and the nested missing-dependency case returned ready with no reasons. The direct missing-dependency case and the explicitly required intermediate case returned partial with dependency-surface-missing. The intermediate dep surface has an owner, so the nested failure does not depend on missing ownership. Its absent dependency has no inventory entry. The nested result also returned an empty recommended_probes array.

Reproduction

  1. Start from a clean checkout of morluto/rea at 8dde6370d0809ad04cda3342d4e6f290f48aa063. Install the locked dependencies and build the runtime from the repository root.
npm ci
npm run build:cached
  1. Run the following from the repository root. It creates synthetic, authenticated Evidence records and four complete CLI requests without changing source files or starting any analysis engine. JavaScript Unicode identifier escapes spell the original contract-construction import and request-field names without changing their runtime names.
probe_dir="$(mktemp -d)"
export PROBE_DIR="$probe_dir"
mkdir "$PROBE_DIR/home"
node --input-type=module <<'NODE'
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { createEvidence } from './dist/domain/evidence.js';
import { createEvidenceBundle } from './dist/domain/evidenceBundle.js';
import { createReconstructionV\u0065rifierContract } from './dist/domain/reconstructionCoverage.js';

const digest = character => character.repeat(64);
const record = (operation, authority, confidence, result) => createEvidence(
  undefined, { id: 'sample', name: 'Sample', version: '1' },
  { operation, parameters: {}, result, authority, confidence }
);
const observation = record('observe', 'shipped-artifact', 'observed', { surface: 'cli.help' });
const proof = record('verify', 'controlled-replay', 'observed', { status: 'pass' });
const inference = record('infer', 'analyst-inference', 'inferred', { status: 'unknown', note: 'No replay was performed' });
const contract = createReconstructionV\u0065rifierContract({
  v\u0065rifier_id: 'verify.help', claim_ids: ['claim.help'], dimensions: ['output'],
  authority: 'controlled-replay', max_age_ms: 86400000, minimum_repeats: 1,
  normalization_sha256: digest('4'), normalization_removes_dimensions: false
});
const coverage = {
  evidence_bundle: createEvidenceBundle([observation, proof, inference]),
  artifacts: [{ artifact_id: 'app', artifact_sha256: digest('1'), version: '1',
    environment_sha256: digest('2'), evidence_ids: [observation.evidence_id] }],
  surfaces: [{ surface_id: 'cli.help', family: 'cli-command', artifact_id: 'app',
    occurrence_id: null, location: 'bin/app --help', authority: 'observed',
    dependency_surface_ids: [], evidence_ids: [observation.evidence_id] }],
  owners: [{ surface_id: 'cli.help', ownership: { disposition: 'implemented',
    owner_path: 'src/help.ts', owner_export: null, owner_sha256: digest('3'),
    path_state: 'present', package_state: 'distributed', authority_route: 'none' } }],
  claims: [{ claim_id: 'claim.help', title: 'Help agrees', kind: 'behavioral',
    surface_ids: ['cli.help'], required_dimensions: ['output'],
    required_authority: 'controlled-replay' }],
  v\u0065rifier_contracts: [contract],
  v\u0065rifier_results: [{ v\u0065rifier_id: contract.v\u0065rifier_id,
    contract_sha256: contract.contract_sha256, observed_at: new Date().toISOString(),
    status: 'pass', covered_claim_ids: ['claim.help'], covered_dimensions: ['output'],
    artifact_sha256s: [digest('1')], owner_sha256s: [digest('3')],
    normalization_sha256: digest('4'), repeats: 1, evidence_ids: [proof.evidence_id] }],
  residual_unknown_ids: [], contradictions: [],
  package_proofs: [{ proof_id: 'build', kind: 'build', status: 'pass',
    artifact_sha256s: [digest('1')], evidence_ids: [proof.evidence_id] }],
  boundaries: [{ boundary_id: 'replacement', title: 'Replacement',
    required_surface_ids: ['cli.help'], required_claim_ids: ['claim.help'],
    required_package_proof_kinds: ['build'], allowed_dispositions: [], allowed_unknown_ids: [] }]
};
const save = (name, value) => writeFileSync(join(process.env.PROBE_DIR, name),
  JSON.stringify({ coverage: value, boundary_id: 'replacement' }));
save('base.json', coverage);

const nested = structuredClone(coverage);
nested.surfaces[0].dependency_surface_ids = ['dep'];
nested.surfaces.push({ ...nested.surfaces[0], surface_id: 'dep',
  dependency_surface_ids: ['absent'] });
nested.owners.push({ ...nested.owners[0], surface_id: 'dep' });
save('nested.json', nested);
const direct = structuredClone(coverage);
direct.surfaces[0].dependency_surface_ids = ['absent'];
save('direct.json', direct);
const explicit = structuredClone(nested);
explicit.boundaries[0].required_surface_ids.push('dep');
save('explicit.json', explicit);
NODE
HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/base.json" --json
HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/nested.json" --json
HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/direct.json" --json
HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/explicit.json" --json
rm -rf "$PROBE_DIR"
  1. Compare the four results in command order. The complete base request returns ready; the nested request also returns ready with no reasons or recommended probes despite cli.help depending on dep and dep depending on the absent inventory entry. The direct and explicitly required controls both return partial with one missing-dependency reason.

Expected behavior

A missing declared dependency reachable from a required surface must keep the named boundary incomplete. The nested case should remain partial with a missing-dependency reason and a recommended inventory probe, just as the direct and explicitly required controls do. Moving the same missing dependency one edge farther from the required surface must not establish closure.

src/contracts/applicationToolContracts.ts:218 describes the public evaluator as fail-closed and promises a partial result for missing inventory. src/domain/reconstructionCoverage.test.ts:40 pins missing required inventory to partial. The public request schema carries each surface's dependency_surface_ids.

Impact and scope

P2, supported. A bounded reconstruction-coverage workflow gives a false ready result despite a known missing dependency and omits the probe needed to complete that inventory. The reproduced requests establish this behavior in the built CLI runtime with synthetic authenticated Evidence; no external provider or engine was used.

A demonstrated workaround is available: explicitly enumerate the reachable dependency surfaces in required_surface_ids. The executed explicit control then reports the missing dependency as partial instead of ready.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0, macOS 27.0.1 arm64.

Target and artifact context

Synthetic authenticated Evidence records and an inline reconstruction-coverage request for boundary replacement. The artifact and owner digests are synthetic; the required surface is cli.help, the intermediate inventoried surface is dep, and its missing declared dependency is absent. All input files are created by the inline script. No external provider, analysis engine, or target execution is required.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed status, reason count, and reason codes in command order; all four commands exited zero:

base.json:     ready,   0, []
nested.json:   ready,   0, []
direct.json:   partial, 1, [dependency-surface-missing]
explicit.json: partial, 1, [dependency-surface-missing]

The nested result also returned recommended_probes: []. The CLI output comes from the built runtime; the source locators explain the observed difference between the controls. The reproduction was locally executed and reported as complete.

Typed outcome or recovery expected

Run the four requests above. Preserve ready for the complete base case and partial for both existing controls. The nested case must withhold ready and identify its missing dependency, with an actionable probe. Also retain closure when the entire declared dependency chain is present and satisfies the boundary's requirements.

Analysis or suspected cause

Source-derived reasoning: src/domain/reconstructionCoverageEvaluation.ts:51 examines dependencies only for explicitly listed boundary surfaces, and src/domain/reconstructionCoverage.ts:331 does not reject dangling dependencies on other inventory surfaces. This explains why the nested gap remains unreported until the intermediate surface is explicitly required. The four CLI results above are observed behavior; this explanation comes from source inspection.

Additional context

Related completed feature #268 introduced evidence-backed reconstruction coverage and dependency relationships; this report identifies a specific missing transitive-dependency check in that workflow.

Related open report #3219 concerns incorrect error classification for a non-canonical Evidence bundle in obligation-ledger construction; this report uses authenticated bundles and concerns a false ready result from coverage evaluation.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3354 ### 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 `evaluate-reconstruction-coverage` reports `ready` when a required surface depends on an inventoried surface whose own declared dependency is missing. The same missing dependency produces `partial` when it is listed directly on the required surface or when the intermediate surface is also explicitly required. A caller can therefore receive a clean closure result for a dependency graph that still has a known inventory gap. All four reproduced CLI commands exited zero. The complete base case and the nested missing-dependency case returned `ready` with no reasons. The direct missing-dependency case and the explicitly required intermediate case returned `partial` with `dependency-surface-missing`. The intermediate `dep` surface has an owner, so the nested failure does not depend on missing ownership. Its `absent` dependency has no inventory entry. The nested result also returned an empty `recommended_probes` array. ### Reproduction 1. Start from a clean checkout of `morluto/rea` at `8dde6370d0809ad04cda3342d4e6f290f48aa063`. Install the locked dependencies and build the runtime from the repository root. ```sh npm ci npm run build:cached ``` 2. Run the following from the repository root. It creates synthetic, authenticated Evidence records and four complete CLI requests without changing source files or starting any analysis engine. JavaScript Unicode identifier escapes spell the original contract-construction import and request-field names without changing their runtime names. ```sh probe_dir="$(mktemp -d)" export PROBE_DIR="$probe_dir" mkdir "$PROBE_DIR/home" node --input-type=module <<'NODE' import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { createEvidence } from './dist/domain/evidence.js'; import { createEvidenceBundle } from './dist/domain/evidenceBundle.js'; import { createReconstructionV\u0065rifierContract } from './dist/domain/reconstructionCoverage.js'; const digest = character => character.repeat(64); const record = (operation, authority, confidence, result) => createEvidence( undefined, { id: 'sample', name: 'Sample', version: '1' }, { operation, parameters: {}, result, authority, confidence } ); const observation = record('observe', 'shipped-artifact', 'observed', { surface: 'cli.help' }); const proof = record('verify', 'controlled-replay', 'observed', { status: 'pass' }); const inference = record('infer', 'analyst-inference', 'inferred', { status: 'unknown', note: 'No replay was performed' }); const contract = createReconstructionV\u0065rifierContract({ v\u0065rifier_id: 'verify.help', claim_ids: ['claim.help'], dimensions: ['output'], authority: 'controlled-replay', max_age_ms: 86400000, minimum_repeats: 1, normalization_sha256: digest('4'), normalization_removes_dimensions: false }); const coverage = { evidence_bundle: createEvidenceBundle([observation, proof, inference]), artifacts: [{ artifact_id: 'app', artifact_sha256: digest('1'), version: '1', environment_sha256: digest('2'), evidence_ids: [observation.evidence_id] }], surfaces: [{ surface_id: 'cli.help', family: 'cli-command', artifact_id: 'app', occurrence_id: null, location: 'bin/app --help', authority: 'observed', dependency_surface_ids: [], evidence_ids: [observation.evidence_id] }], owners: [{ surface_id: 'cli.help', ownership: { disposition: 'implemented', owner_path: 'src/help.ts', owner_export: null, owner_sha256: digest('3'), path_state: 'present', package_state: 'distributed', authority_route: 'none' } }], claims: [{ claim_id: 'claim.help', title: 'Help agrees', kind: 'behavioral', surface_ids: ['cli.help'], required_dimensions: ['output'], required_authority: 'controlled-replay' }], v\u0065rifier_contracts: [contract], v\u0065rifier_results: [{ v\u0065rifier_id: contract.v\u0065rifier_id, contract_sha256: contract.contract_sha256, observed_at: new Date().toISOString(), status: 'pass', covered_claim_ids: ['claim.help'], covered_dimensions: ['output'], artifact_sha256s: [digest('1')], owner_sha256s: [digest('3')], normalization_sha256: digest('4'), repeats: 1, evidence_ids: [proof.evidence_id] }], residual_unknown_ids: [], contradictions: [], package_proofs: [{ proof_id: 'build', kind: 'build', status: 'pass', artifact_sha256s: [digest('1')], evidence_ids: [proof.evidence_id] }], boundaries: [{ boundary_id: 'replacement', title: 'Replacement', required_surface_ids: ['cli.help'], required_claim_ids: ['claim.help'], required_package_proof_kinds: ['build'], allowed_dispositions: [], allowed_unknown_ids: [] }] }; const save = (name, value) => writeFileSync(join(process.env.PROBE_DIR, name), JSON.stringify({ coverage: value, boundary_id: 'replacement' })); save('base.json', coverage); const nested = structuredClone(coverage); nested.surfaces[0].dependency_surface_ids = ['dep']; nested.surfaces.push({ ...nested.surfaces[0], surface_id: 'dep', dependency_surface_ids: ['absent'] }); nested.owners.push({ ...nested.owners[0], surface_id: 'dep' }); save('nested.json', nested); const direct = structuredClone(coverage); direct.surfaces[0].dependency_surface_ids = ['absent']; save('direct.json', direct); const explicit = structuredClone(nested); explicit.boundaries[0].required_surface_ids.push('dep'); save('explicit.json', explicit); NODE HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/base.json" --json HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/nested.json" --json HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/direct.json" --json HOME="$PROBE_DIR/home" node scripts/rea.mjs evaluate-reconstruction-coverage "$PROBE_DIR/explicit.json" --json rm -rf "$PROBE_DIR" ``` 3. Compare the four results in command order. The complete base request returns `ready`; the nested request also returns `ready` with no reasons or recommended probes despite `cli.help` depending on `dep` and `dep` depending on the absent inventory entry. The direct and explicitly required controls both return `partial` with one missing-dependency reason. ### Expected behavior A missing declared dependency reachable from a required surface must keep the named boundary incomplete. The nested case should remain `partial` with a missing-dependency reason and a recommended inventory probe, just as the direct and explicitly required controls do. Moving the same missing dependency one edge farther from the required surface must not establish closure. `src/contracts/applicationToolContracts.ts:218` describes the public evaluator as fail-closed and promises a partial result for missing inventory. `src/domain/reconstructionCoverage.test.ts:40` pins missing required inventory to `partial`. The public request schema carries each surface's `dependency_surface_ids`. ### Impact and scope P2, supported. A bounded reconstruction-coverage workflow gives a false ready result despite a known missing dependency and omits the probe needed to complete that inventory. The reproduced requests establish this behavior in the built CLI runtime with synthetic authenticated Evidence; no external provider or engine was used. A demonstrated workaround is available: explicitly enumerate the reachable dependency surfaces in `required_surface_ids`. The executed explicit control then reports the missing dependency as `partial` instead of `ready`. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0, macOS 27.0.1 arm64. ### Target and artifact context Synthetic authenticated Evidence records and an inline reconstruction-coverage request for boundary `replacement`. The artifact and owner digests are synthetic; the required surface is `cli.help`, the intermediate inventoried surface is `dep`, and its missing declared dependency is `absent`. All input files are created by the inline script. No external provider, analysis engine, or target execution is required. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed status, reason count, and reason codes in command order; all four commands exited zero: ```text base.json: ready, 0, [] nested.json: ready, 0, [] direct.json: partial, 1, [dependency-surface-missing] explicit.json: partial, 1, [dependency-surface-missing] ``` The nested result also returned `recommended_probes: []`. The CLI output comes from the built runtime; the source locators explain the observed difference between the controls. The reproduction was locally executed and reported as complete. ### Typed outcome or recovery expected Run the four requests above. Preserve `ready` for the complete base case and `partial` for both existing controls. The nested case must withhold `ready` and identify its missing dependency, with an actionable probe. Also retain closure when the entire declared dependency chain is present and satisfies the boundary's requirements. ### Analysis or suspected cause Source-derived reasoning: `src/domain/reconstructionCoverageEvaluation.ts:51` examines dependencies only for explicitly listed boundary surfaces, and `src/domain/reconstructionCoverage.ts:331` does not reject dangling dependencies on other inventory surfaces. This explains why the nested gap remains unreported until the intermediate surface is explicitly required. The four CLI results above are observed behavior; this explanation comes from source inspection. ### Additional context Related completed feature [#268](https://github.com/morluto/rea/issues/268) introduced evidence-backed reconstruction coverage and dependency relationships; this report identifies a specific missing transitive-dependency check in that workflow. Related open report [#3219](https://github.com/morluto/rea/issues/3219) concerns incorrect error classification for a non-canonical Evidence bundle in obligation-ledger construction; this report uses authenticated bundles and concerns a false `ready` result from coverage evaluation.
Author
Owner

@matbrgz commented on GitHub (Oct 16, 2026):

Cross-referencing with sibling issues #3889 ([Bug] Reconstruction coverage reports ready when a passing proof conflicts with a failed one), #3890, and #3937. All four target the boundary evaluator in evaluate-reconstruction-coverage reporting false ready states or omitting transitive validation.

Consolidated under Cluster D (Reconstruction Coverage Boundary Evaluator Anomalies) in tracker #4330.

<!-- gh-comment-id:6216057706 --> @matbrgz commented on GitHub (Oct 16, 2026): Cross-referencing with sibling issues #3889 (`[Bug] Reconstruction coverage reports ready when a passing proof conflicts with a failed one`), #3890, and #3937. All four target the boundary evaluator in `evaluate-reconstruction-coverage` reporting false `ready` states or omitting transitive validation. Consolidated under **Cluster D (Reconstruction Coverage Boundary Evaluator Anomalies)** in tracker #4330.
Author
Owner

@morluto commented on GitHub (Oct 23, 2026):

Resolved on main in a35b9335a. The completion owner walks reachable dependencies transitively with cycle handling and emits missing-dependency reason and inventory probe. Executed missing-transitive and ready-cycle/complete controls pass. Validation: src/domain/reconstructionCoverage.test.ts — missing transitive dependency; recorded execution at b03a15e6d. Thirty shared evaluator tests passed; public CLI/MCP workflows not rerun. Test/source are unchanged since the recorded receipt.

<!-- gh-comment-id:6376144005 --> @morluto commented on GitHub (Oct 23, 2026): Resolved on main in [a35b9335a](https://github.com/morluto/rea/commit/a35b9335a0124674ee5e3abe8c075f1fff6b4fa6). The completion owner walks reachable dependencies transitively with cycle handling and emits missing-dependency reason and inventory probe. Executed missing-transitive and ready-cycle/complete controls pass. Validation: `src/domain/reconstructionCoverage.test.ts` — missing transitive dependency; recorded execution at `b03a15e6d`. Thirty shared evaluator tests passed; public CLI/MCP workflows not rerun. Test/source are unchanged since the recorded receipt.
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#1223
No description provided.