[GH-ISSUE #3890] [Bug] Reconstruction coverage ignores authority routing on surfaces named only by a required claim #1511

Closed
opened 2026-10-16 21:06:23 +00:00 by bsakaguchi · 4 comments
Owner

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

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

rea evaluate-reconstruction-coverage reports a boundary ready when a required claim covers a surface whose owner is marked authority_route: "detected", as long as that surface is not also listed in the boundary's required_surface_ids. The owner's digest still backs the accepted verifier result. Listing the same surface in required_surface_ids, with no other change, returns failed with authority-routing-detected.

claim-routing returns status: "ready" with one required surface, one required claim, and no reasons or probes. claim-routing-explicit, which only adds cli.background to required_surface_ids, returns failed with authority-routing-detected on cli.background. Both commands exit 0.

Reproduction

  1. From the root of a built source checkout (the generator imports dist/ modules), write two request files into a scratch directory. The required claim claim.help covers cli.help and cli.background; the cli.background owner has authority_route: "detected" and its digest is listed in the verifier result's owner_sha256s. The second request differs only by adding cli.background to the boundary's required_surface_ids.
OUT=$(mktemp -d)
node --input-type=module - "$PWD" "$OUT" <<'JS'
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
const [repo, out] = process.argv.slice(2);
const load = p => import(pathToFileURL(join(repo, 'dist', p)));
const {createEvidence} = await load('domain/evidence.js');
const {createEvidenceBundle} = await load('domain/evidenceBundle.js');
const {createReconstructionVerifierContract} = await load('domain/reconstructionCoverage.js');
const d = n => n.repeat(64);
const evidence = (operation, authority, result) => createEvidence(undefined,{id:'fixture',name:'Fixture',version:'1'},{operation,parameters:{},result,authority,confidence:'observed'});
const observation=evidence('inspect_fixture','shipped-artifact',{found:true});
const proof=evidence('verify_fixture','controlled-replay',{passed:true});
const failure=evidence('verify_failed_build','controlled-replay',{passed:false});
const contract=createReconstructionVerifierContract({verifier_id:'verify.help',claim_ids:['claim.help'],dimensions:['output','exit'],authority:'controlled-replay',max_age_ms:86400000,minimum_repeats:1,normalization_sha256:d('4'),normalization_removes_dimensions:false});
const owner={disposition:'implemented',owner_path:'src/help.js',owner_export:'help',owner_sha256:d('3'),path_state:'present',package_state:'distributed',authority_route:'none'};
const surface={surface_id:'cli.help',family:'cli-command',artifact_id:'app',occurrence_id:null,location:'app --help',authority:'observed',dependency_surface_ids:[],evidence_ids:[observation.evidence_id]};
const base={evidence_bundle:createEvidenceBundle([observation,proof,failure]),artifacts:[{artifact_id:'app',artifact_sha256:d('1'),version:'1',environment_sha256:d('2'),evidence_ids:[observation.evidence_id]}],surfaces:[surface],owners:[{surface_id:'cli.help',ownership:owner}],claims:[{claim_id:'claim.help',title:'Help matches',kind:'behavioral',surface_ids:['cli.help'],required_dimensions:['output','exit'],required_authority:'controlled-replay'}],verifier_contracts:[contract],verifier_results:[{verifier_id:contract.verifier_id,contract_sha256:contract.contract_sha256,observed_at:new Date().toISOString(),status:'pass',covered_claim_ids:['claim.help'],covered_dimensions:['output','exit'],artifact_sha256s:[d('1')],owner_sha256s:[d('3')],normalization_sha256:d('4'),repeats:1,evidence_ids:[proof.evidence_id]}],residual_unknown_ids:[],contradictions:[],package_proofs:[{proof_id:'build.pass',kind:'build',status:'pass',artifact_sha256s:[d('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,c)=>writeFileSync(join(out,name+'.json'),JSON.stringify({coverage:c,boundary_id:'replacement'}));
const omitted=structuredClone(base);
omitted.surfaces.push({...surface,surface_id:'cli.background',location:'app background'});
omitted.claims[0].surface_ids.push('cli.background');
omitted.owners.push({surface_id:'cli.background',ownership:{...owner,owner_path:'src/background.js',owner_sha256:d('5'),authority_route:'detected'}});
omitted.verifier_results[0].owner_sha256s.push(d('5'));
save('claim-routing',omitted);
const explicit=structuredClone(omitted);
explicit.boundaries[0].required_surface_ids.push('cli.background');
save('claim-routing-explicit',explicit);
JS
  1. Evaluate both requests through the CLI (node scripts/rea.mjs is the rea bin entry in the checkout).
node scripts/rea.mjs evaluate-reconstruction-coverage "$OUT/claim-routing.json" --json
node scripts/rea.mjs evaluate-reconstruction-coverage "$OUT/claim-routing-explicit.json" --json

Expected behavior

The tool contract promises that authority routing fails closed. A required claim explicitly covers cli.background, whose owner is recorded as routing into the authority and participates in the claim's verifier digest commitments, so closure must not report the claim replacement-ready while ignoring that detected delegation. Either result should be failed with authority-routing-detected for cli.background; rejecting a boundary whose claim scope exceeds its surface scope would also be safe.

Impact and scope

A boundary whose required claims name additional surfaces is reported replacement-ready despite a detected authority delegation by an owner backing those claims, contradicting the fail-closed contract for authority routing.

Workaround: available. Including every surface referenced by every required claim in required_surface_ids makes the evaluator report authority-routing-detected, as the explicit control shows.

Reporter's impact estimate: P2 (supported).

REA version

6.5.0, built from source at main commit 034342ba28 (latest npm release is 6.5.0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64; No analysis provider or target execution is involved; the input is authenticated synthetic Evidence.

Target and artifact context

Synthetic reconstruction coverage request with authenticated Evidence bundle

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: src/contracts/applicationToolContracts.ts:221 (evaluate_reconstruction_coverage description: "authority routing fail closed"); src/domain/reconstructionCoverageEvaluation.ts:136 defines authority-routing-detected, and src/domain/reconstructionCoverage.ts:390 classifies it as failed closure
  • Failure source: public CLI results for the two requests above; static cause in src/domain/reconstructionCoverage.ts:337 and src/domain/reconstructionCoverageEvaluation.ts:51, 155, 350-378
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Relevant result fields, in command order:

claim-routing: status="ready", summary={"required_surfaces":1,"required_claims":1,"reasons":0}, reasons=[], recommended_probes=[]
claim-routing-explicit: status="failed", summary={"required_surfaces":2,"required_claims":1,"reasons":1}, reasons=[{"code":"authority-routing-detected","subject_id":"cli.background","detail":"Reconstructed owner routes into the authority."}]

Source-derived cause: src/domain/reconstructionCoverage.ts:337 checks only that claim surfaces exist, not that they fall inside the boundary's surface scope. src/domain/reconstructionCoverageEvaluation.ts:51 runs ownership checks only for required_surface_ids. Required claims are evaluated separately from line 155; lines 350-378 require the claim owners' digests in the verifier result but do not evaluate owner readiness or routing. The background owner's digest therefore contributes to accepted proof while its recorded routing failure is skipped.

Typed outcome or recovery expected

A failed coverage result with authority-routing-detected for the claim-referenced surface, or a validation rejection of a boundary whose required claims reference surfaces outside required_surface_ids.

Restoration check:

On claim-routing, the tested build returns status: "ready" with no reasons; a fixed build should return a non-ready result that names cli.background (for example authority-routing-detected) or rejects the mismatched claim and boundary scopes. The claim-routing-explicit control must keep returning failed with authority-routing-detected, and the same request with the background owner's authority_route set to "none" should not gain a routing reason.

Analysis or suspected cause

Source-derived: src/domain/reconstructionCoverageEvaluation.ts:51 checks ownership only for required_surface_ids; required claims (line 155, lines 350-378) require owner digests in verifier results but never evaluate owner routing, and src/domain/reconstructionCoverage.ts:337 does not require claim surfaces to be inside the boundary scope.

Additional context

Related but distinct open reports on the same evaluator: #3354 (missing transitive dependency inventory; this case uses no dependency edges) and #3357 (Evidence authority validation, a different branch). The routing state here is a declared synthetic value, not an observed external execution.

This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3890 ### 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 `rea evaluate-reconstruction-coverage` reports a boundary `ready` when a required claim covers a surface whose owner is marked `authority_route: "detected"`, as long as that surface is not also listed in the boundary's `required_surface_ids`. The owner's digest still backs the accepted verifier result. Listing the same surface in `required_surface_ids`, with no other change, returns `failed` with `authority-routing-detected`. `claim-routing` returns `status: "ready"` with one required surface, one required claim, and no reasons or probes. `claim-routing-explicit`, which only adds `cli.background` to `required_surface_ids`, returns `failed` with `authority-routing-detected` on `cli.background`. Both commands exit 0. ### Reproduction 1. From the root of a built source checkout (the generator imports `dist/` modules), write two request files into a scratch directory. The required claim `claim.help` covers `cli.help` and `cli.background`; the `cli.background` owner has `authority_route: "detected"` and its digest is listed in the verifier result's `owner_sha256s`. The second request differs only by adding `cli.background` to the boundary's `required_surface_ids`. ```sh OUT=$(mktemp -d) node --input-type=module - "$PWD" "$OUT" <<'JS' import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; const [repo, out] = process.argv.slice(2); const load = p => import(pathToFileURL(join(repo, 'dist', p))); const {createEvidence} = await load('domain/evidence.js'); const {createEvidenceBundle} = await load('domain/evidenceBundle.js'); const {createReconstructionVerifierContract} = await load('domain/reconstructionCoverage.js'); const d = n => n.repeat(64); const evidence = (operation, authority, result) => createEvidence(undefined,{id:'fixture',name:'Fixture',version:'1'},{operation,parameters:{},result,authority,confidence:'observed'}); const observation=evidence('inspect_fixture','shipped-artifact',{found:true}); const proof=evidence('verify_fixture','controlled-replay',{passed:true}); const failure=evidence('verify_failed_build','controlled-replay',{passed:false}); const contract=createReconstructionVerifierContract({verifier_id:'verify.help',claim_ids:['claim.help'],dimensions:['output','exit'],authority:'controlled-replay',max_age_ms:86400000,minimum_repeats:1,normalization_sha256:d('4'),normalization_removes_dimensions:false}); const owner={disposition:'implemented',owner_path:'src/help.js',owner_export:'help',owner_sha256:d('3'),path_state:'present',package_state:'distributed',authority_route:'none'}; const surface={surface_id:'cli.help',family:'cli-command',artifact_id:'app',occurrence_id:null,location:'app --help',authority:'observed',dependency_surface_ids:[],evidence_ids:[observation.evidence_id]}; const base={evidence_bundle:createEvidenceBundle([observation,proof,failure]),artifacts:[{artifact_id:'app',artifact_sha256:d('1'),version:'1',environment_sha256:d('2'),evidence_ids:[observation.evidence_id]}],surfaces:[surface],owners:[{surface_id:'cli.help',ownership:owner}],claims:[{claim_id:'claim.help',title:'Help matches',kind:'behavioral',surface_ids:['cli.help'],required_dimensions:['output','exit'],required_authority:'controlled-replay'}],verifier_contracts:[contract],verifier_results:[{verifier_id:contract.verifier_id,contract_sha256:contract.contract_sha256,observed_at:new Date().toISOString(),status:'pass',covered_claim_ids:['claim.help'],covered_dimensions:['output','exit'],artifact_sha256s:[d('1')],owner_sha256s:[d('3')],normalization_sha256:d('4'),repeats:1,evidence_ids:[proof.evidence_id]}],residual_unknown_ids:[],contradictions:[],package_proofs:[{proof_id:'build.pass',kind:'build',status:'pass',artifact_sha256s:[d('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,c)=>writeFileSync(join(out,name+'.json'),JSON.stringify({coverage:c,boundary_id:'replacement'})); const omitted=structuredClone(base); omitted.surfaces.push({...surface,surface_id:'cli.background',location:'app background'}); omitted.claims[0].surface_ids.push('cli.background'); omitted.owners.push({surface_id:'cli.background',ownership:{...owner,owner_path:'src/background.js',owner_sha256:d('5'),authority_route:'detected'}}); omitted.verifier_results[0].owner_sha256s.push(d('5')); save('claim-routing',omitted); const explicit=structuredClone(omitted); explicit.boundaries[0].required_surface_ids.push('cli.background'); save('claim-routing-explicit',explicit); JS ``` 2. Evaluate both requests through the CLI (`node scripts/rea.mjs` is the `rea` bin entry in the checkout). ```sh node scripts/rea.mjs evaluate-reconstruction-coverage "$OUT/claim-routing.json" --json node scripts/rea.mjs evaluate-reconstruction-coverage "$OUT/claim-routing-explicit.json" --json ``` ### Expected behavior The tool contract promises that authority routing fails closed. A required claim explicitly covers `cli.background`, whose owner is recorded as routing into the authority and participates in the claim's verifier digest commitments, so closure must not report the claim replacement-ready while ignoring that detected delegation. Either result should be `failed` with `authority-routing-detected` for `cli.background`; rejecting a boundary whose claim scope exceeds its surface scope would also be safe. ### Impact and scope A boundary whose required claims name additional surfaces is reported replacement-ready despite a detected authority delegation by an owner backing those claims, contradicting the fail-closed contract for authority routing. Workaround: available. Including every surface referenced by every required claim in `required_surface_ids` makes the evaluator report `authority-routing-detected`, as the explicit control shows. Reporter's impact estimate: P2 (supported). ### REA version 6.5.0, built from source at main commit 034342ba28c8e56c3746be92b78aa7299afa02bd (latest npm release is 6.5.0) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64; No analysis provider or target execution is involved; the input is authenticated synthetic Evidence. ### Target and artifact context Synthetic reconstruction coverage request with authenticated Evidence bundle ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: src/contracts/applicationToolContracts.ts:221 (`evaluate_reconstruction_coverage` description: "authority routing fail closed"); src/domain/reconstructionCoverageEvaluation.ts:136 defines `authority-routing-detected`, and src/domain/reconstructionCoverage.ts:390 classifies it as failed closure - Failure source: public CLI results for the two requests above; static cause in src/domain/reconstructionCoverage.ts:337 and src/domain/reconstructionCoverageEvaluation.ts:51, 155, 350-378 - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Relevant result fields, in command order: ```text claim-routing: status="ready", summary={"required_surfaces":1,"required_claims":1,"reasons":0}, reasons=[], recommended_probes=[] claim-routing-explicit: status="failed", summary={"required_surfaces":2,"required_claims":1,"reasons":1}, reasons=[{"code":"authority-routing-detected","subject_id":"cli.background","detail":"Reconstructed owner routes into the authority."}] ``` Source-derived cause: src/domain/reconstructionCoverage.ts:337 checks only that claim surfaces exist, not that they fall inside the boundary's surface scope. src/domain/reconstructionCoverageEvaluation.ts:51 runs ownership checks only for `required_surface_ids`. Required claims are evaluated separately from line 155; lines 350-378 require the claim owners' digests in the verifier result but do not evaluate owner readiness or routing. The background owner's digest therefore contributes to accepted proof while its recorded routing failure is skipped. ### Typed outcome or recovery expected A failed coverage result with authority-routing-detected for the claim-referenced surface, or a validation rejection of a boundary whose required claims reference surfaces outside required_surface_ids. Restoration check: On `claim-routing`, the tested build returns `status: "ready"` with no reasons; a fixed build should return a non-ready result that names `cli.background` (for example `authority-routing-detected`) or rejects the mismatched claim and boundary scopes. The `claim-routing-explicit` control must keep returning `failed` with `authority-routing-detected`, and the same request with the background owner's `authority_route` set to `"none"` should not gain a routing reason. ### Analysis or suspected cause Source-derived: src/domain/reconstructionCoverageEvaluation.ts:51 checks ownership only for required_surface_ids; required claims (line 155, lines 350-378) require owner digests in verifier results but never evaluate owner routing, and src/domain/reconstructionCoverage.ts:337 does not require claim surfaces to be inside the boundary scope. ### Additional context Related but distinct open reports on the same evaluator: #3354 (missing transitive dependency inventory; this case uses no dependency edges) and #3357 (Evidence authority validation, a different branch). The routing state here is a declared synthetic value, not an observed external execution. This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

The inconsistent boundary evaluation is the core issue here: the same surface with the same authority_route: "detected" owner is judged ready when reached only via a required claim, but failed when also listed in required_surface_ids. That means coverage can quietly accept a boundary backed by a detected-route digest as long as the surface name never appears explicitly — a false sense of verification. The routing detection should apply uniformly to any surface whose owner is authority-routed, regardless of whether it's named by claim or by surface id; otherwise the ready/failed outcome depends on how the boundary is expressed rather than on the actual evidence. Suggest aligning both paths to fail on authority-routing-detected and adding a regression case that flips only required_surface_ids as the reporter did.

<!-- gh-comment-id:6185619345 --> @XinwangTao commented on GitHub (Oct 15, 2026): The inconsistent boundary evaluation is the core issue here: the same surface with the same authority_route: "detected" owner is judged ready when reached only via a required claim, but failed when also listed in required_surface_ids. That means coverage can quietly accept a boundary backed by a detected-route digest as long as the surface name never appears explicitly — a false sense of verification. The routing detection should apply uniformly to any surface whose owner is authority-routed, regardless of whether it's named by claim or by surface id; otherwise the ready/failed outcome depends on how the boundary is expressed rather than on the actual evidence. Suggest aligning both paths to fail on authority-routing-detected and adding a regression case that flips only required_surface_ids as the reporter did.
Author
Owner

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

Cross-referencing with canonical issue #3354 and siblings #3889, #3937 regarding reconstruction coverage authority routing and claim surface validation. Cataloged under Cluster BR in consolidation tracker #4330.

<!-- gh-comment-id:6297982341 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #3354 and siblings #3889, #3937 regarding reconstruction coverage authority routing and claim surface validation. Cataloged under Cluster BR in consolidation tracker #4330.
Author
Owner

@XinwangTao commented on GitHub (Oct 21, 2026):

The flip-only-required_surface_ids regression is the right pin for this one, and it pairs naturally with the contradiction case in #3937: both are verdicts that change solely because of redundant association metadata. If Draft #4337 addresses both, worth adding one combined test that feeds the same boundary in claim-named vs surface-id-named forms and asserts identical reason/probe output — that guarantees the two fixes converge instead of patching each divergent path independently.

<!-- gh-comment-id:6305759617 --> @XinwangTao commented on GitHub (Oct 21, 2026): The flip-only-required_surface_ids regression is the right pin for this one, and it pairs naturally with the contradiction case in #3937: both are verdicts that change solely because of redundant association metadata. If Draft #4337 addresses both, worth adding one combined test that feeds the same boundary in claim-named vs surface-id-named forms and asserts identical reason/probe output — that guarantees the two fixes converge instead of patching each divergent path independently.
Author
Owner

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

Resolved on main in a35b9335a. Claim-only surfaces enter the shared effective completion scope and owner routing checks. Executed claim-only authority routing returns failed with authority-routing-detected. Validation: src/domain/reconstructionCoverage.test.ts — claim-only authority routing; recorded execution at b03a15e6d. Thirty coverage tests passed at the shared evaluator boundary; public adapter replay unexecuted. No real-provider authority qualification is claimed.

<!-- gh-comment-id:6376141931 --> @morluto commented on GitHub (Oct 23, 2026): Resolved on main in [a35b9335a](https://github.com/morluto/rea/commit/a35b9335a0124674ee5e3abe8c075f1fff6b4fa6). Claim-only surfaces enter the shared effective completion scope and owner routing checks. Executed claim-only authority routing returns failed with authority-routing-detected. Validation: `src/domain/reconstructionCoverage.test.ts` — claim-only authority routing; recorded execution at `b03a15e6d`. Thirty coverage tests passed at the shared evaluator boundary; public adapter replay unexecuted. No real-provider authority qualification is claimed.
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#1511
No description provided.