diff --git a/dag/gunbc/discovery_census.dag b/dag/gunbc/discovery_census.dag index bd4bdacd1cc..bbcc0727a97 100644 --- a/dag/gunbc/discovery_census.dag +++ b/dag/gunbc/discovery_census.dag @@ -1,7 +1,7 @@ module gunbc.discovery_census import extdeps.bazel.label { - Label, + Label, PackagePath, LabelParse, LabelParsed, LabelRefused, LabelRefusal, parse_label, render_label, label_eq, @@ -18,7 +18,8 @@ import v2.workflow.required_floor { } import v2.std.live_tree { LiveTreeDisposition } import std.optional { Optional, Absent, Present, optional_absent, optional_present } -import std.types { String, Bool } +import std.types { String, Bool, NonEmptyStr } +import extdeps.bazel.target_pattern { TargetPattern, SubtreeTargets } import std.integer { Int } // EVERY DISCOVERED SITE GETS A ROW, AND THE ROW IS THE JOIN THIS MODULE EXISTS FOR. @@ -136,6 +137,17 @@ fn site_label(site: DiscoveredSite) -> SiteLabelParse { } } +// THE SUBTREE THE CLAIM LABELS OCCUPY, AND THE CLAIM ROUTE'S UNIVERSE. Witness claims are authored +// in the `test.claim` module namespace, so `site_label` maps every one of them under +// `//test/claim/...`; `gunbc test` admits an operand contained in this pattern to the claim route +// (`gunbc.target_invocation` `admit_test_operand`), whose executor selects the discovered sites the +// operand contains. It is a LABEL-SPACE fact derived from the module namespace, not a copy of the +// discovery roots: which files are walked stays the discovery authority's, and a claim module +// authored outside `test.claim` is not addressable through this route. +fn claim_route_universe() -> TargetPattern { + SubtreeTargets { package: PackagePath { segments: ["test" as NonEmptyStr, "claim" as NonEmptyStr] } } +} + // --------------------------------------------------------------------------------------------- // THE CENSUS FOLD // --------------------------------------------------------------------------------------------- diff --git a/dag/gunbc/target_invocation.dag b/dag/gunbc/target_invocation.dag index 7bdabbb04c7..e86e5404c32 100644 --- a/dag/gunbc/target_invocation.dag +++ b/dag/gunbc/target_invocation.dag @@ -15,7 +15,7 @@ import gunbc.target_binding { TargetProducerBound, KnownTargetBindingMissing, TargetUnknown, TargetLookupLabelNotCanonical, build_target_binding_index, target_binding_lookup, } -import std.types { String } +import std.types { String, Bool, List } import std.integer { Int } import extdeps.bazel.target_pattern { TargetPattern, SingleTarget, PackageTargets, SubtreeTargets, @@ -49,18 +49,35 @@ import extdeps.bazel.target_pattern { // `PatternAllTargetsUnsupported` have different remedies from each other and from every // `PatternLabelRefused` inner cause, and collapsing them into one malformed-operand arm would be // the state-space conflation this repository keeps finding. +// +// THE CLAIM ROUTE IS THE SECOND UNIVERSE, DECIDED BY THE SAME CONTAINMENT. Witness claims -- the +// `test fn` declarations the floor's discovery authority enrolls -- are labelled by +// `gunbc.discovery_census` `site_label` (`a.b.c` + `f` -> `//a/b/c:f`), and the subtree those +// labels occupy is `gunbc.discovery_census` `claim_route_universe`. An operand inside it -- an exact +// claim label, a module's `:all`, or a `/...` subtree -- is `TestOperandClaimRoute` and enters the +// claim executor carrying that pattern; the executor selects the discovered claims the pattern +// contains and evaluates them with the floor's own claim evaluation. The native universe is asked +// FIRST, so no operand is answered by both. Like the native universe, the claim universe is a +// parameter the production caller supplies. type TestOperandAdmission = TestOperandExactTarget { label: Label } | TestOperandNativeRoute { pattern: TargetPattern } + | TestOperandClaimRoute { pattern: TargetPattern } | TestOperandSetForm { pattern: TargetPattern } | TestOperandRefused { operand: String, cause: TargetPatternRefusal } -fn admit_test_operand(operand: String, native_universe: TargetPattern) -> TestOperandAdmission { +fn admit_test_operand( + operand: String, + native_universe: TargetPattern, + claim_universe: TargetPattern +) -> TestOperandAdmission { match parse_target_pattern(text: operand) { TargetPatternRefused { cause: c } => TestOperandRefused { operand: operand, cause: c } TargetPatternParsed { pattern: p } => if target_pattern_within(inner: p, outer: native_universe) { TestOperandNativeRoute { pattern: p } + } else if target_pattern_within(inner: p, outer: claim_universe) { + TestOperandClaimRoute { pattern: p } } else { match p { SingleTarget { label: l } => TestOperandExactTarget { label: l } @@ -71,24 +88,83 @@ fn admit_test_operand(operand: String, native_universe: TargetPattern) -> TestOp } } -// A SET FORM OUTSIDE THE NATIVE UNIVERSE HAS NO EXECUTOR, AND THAT IS WHAT THIS REFUSES. +// A SET FORM OUTSIDE EVERY ROUTE'S UNIVERSE HAS NO EXECUTOR, AND THAT IS WHAT THIS REFUSES. // -// The 2026-09-21 ruling this arm carried said set forms run only through the native test route and -// that the route did not exist. The route exists now and this module routes to it, so the refusal -// is no longer about the route's availability -- it is about the operand. `//dag/test/claim/...` -// names a population the native lane's universe does not contain and the instrument registry -// cannot enumerate, so nothing may answer for it; the verb refuses with status 2 -- no observation -// -- never 1, which would claim a run happened and did not hold. Delegating it to the interpreter -// stays forbidden for the reason it always was: an interpreted run would report a result the -// native compiler never produced. -// -// RETIREMENT TRIGGER: an executor that enumerates a population outside the native universe. The -// instrument registry is not one -- it answers exact labels by construction. +// Two executors enumerate a population: the native test route (`//v2/test/...`) and the claim +// route (`//test/claim/...`, the discovered witness claims). The retirement trigger this arm used to +// carry -- "an executor that enumerates a population outside the native universe" -- is satisfied +// for the claim population, so `//test/claim/...` is no longer refused here. A set form outside +// BOTH, such as `//dag/test/claim/...` (a file-path spelling, not a claim label), still names no +// population any executor enumerates, and the instrument registry is not one -- it answers exact +// labels by construction. The verb refuses with status 2 -- no observation -- never 1, which would +// claim a run happened and did not hold. A set form is never widened into another route's +// universe: that would report a run over a population the operand did not name, and handing a +// native-universe operand to the interpreter would report a result the native compiler never +// produced. fn test_operand_set_form_refusal_rendered(operand: String) -> String { concat( concat("gunbc test: ", operand), - " denotes a SET of targets outside the native test route's universe; no executor enumerates that population, and set forms are never delegated to the interpreter" + " denotes a SET of targets outside every route's universe (the native test route and the claim route); no executor enumerates that population, and a set form is never widened into another route" + ) +} + +// THE CLAIM ROUTE'S MEMBER VERDICTS, AND THE FOLD THAT MAKES THEM THE OPERAND'S TERMINATION. +// +// One verdict per selected claim. `ClaimDidNotHold` is an OBSERVATION -- the claim evaluated and +// returned false or a non-Bool value. `ClaimUnobserved` is the absence of one -- a runtime error, a +// host refusal, an unwind, a budget interrupt -- and is not reported as a failing claim, for the +// same reason `InvocationSubjectUnreached` is not `InvocationObservationDidNotHold`. +type ClaimRouteVerdict + = ClaimHeld + | ClaimDidNotHold + | ClaimUnobserved + +type ClaimRouteTally { + did_not_hold: Bool + unobserved: Bool + members: Int +} + +fn claim_route_tally_step(acc: ClaimRouteTally, v: ClaimRouteVerdict) -> ClaimRouteTally { + match v { + ClaimHeld => ClaimRouteTally { + did_not_hold: acc.did_not_hold, + unobserved: acc.unobserved, + members: acc.members + 1 + } + ClaimDidNotHold => ClaimRouteTally { + did_not_hold: true, + unobserved: acc.unobserved, + members: acc.members + 1 + } + ClaimUnobserved => ClaimRouteTally { + did_not_hold: acc.did_not_hold, + unobserved: true, + members: acc.members + 1 + } + } +} + +// A DEFINITE FAILURE DOMINATES; AN EMPTY SELECTION IS UNREACHED. A claim that returned false is a +// finding whatever happened beside it; a selection with an unobserved member cannot answer "held"; +// and a pattern that selected nothing produced no evidence, so it is `InvocationSubjectUnreached` +// (exit 2), never a vacuous pass -- the absence-is-not-success rule `gunbc.instrument_targets` +// `native_route_member_termination` keeps for the native route. +fn claim_route_termination(verdicts: List) -> InvocationTermination { + let tally = fold( + verdicts, + init: ClaimRouteTally { did_not_hold: false, unobserved: false, members: 0 }, + f: (acc, v) => claim_route_tally_step(acc: acc, v: v) ) + if tally.did_not_hold { + InvocationObservationDidNotHold {} + } else if tally.unobserved { + InvocationSubjectUnreached {} + } else if tally.members == 0 { + InvocationSubjectUnreached {} + } else { + InvocationObservationHeld {} + } } // A REFUSED OPERAND TERMINATES AS REFUSED, AND THE RENDERING NAMES THE AUTHORITY'S CAUSE. The diff --git a/dag/test/claim/target_invocation_witness_test.dag b/dag/test/claim/target_invocation_witness_test.dag index 80ee450d456..dc9e88a63d7 100644 --- a/dag/test/claim/target_invocation_witness_test.dag +++ b/dag/test/claim/target_invocation_witness_test.dag @@ -59,6 +59,8 @@ import gunbc.target_invocation { route_target_invocation, route_target_invocation_in, invocation_exit_status, TestOperandExactTarget, TestOperandSetForm, TestOperandNativeRoute, TestOperandRefused, admit_test_operand, test_operand_refusal_rendered, test_operand_set_form_refusal_rendered, + TestOperandClaimRoute, ClaimRouteVerdict, ClaimHeld, ClaimDidNotHold, ClaimUnobserved, + claim_route_termination, } // `TargetPattern` IS DELIBERATELY NOT IMPORTED HERE, and the omission is the point. This module // already imports `extdeps.bazel.label`'s `TargetPattern`, which is a `LabelRefusal` CONSTRUCTOR @@ -113,7 +115,7 @@ import gunbc.build_target { import gunbc.discovery_census { DiscoveredSite, SiteCensus, CensusAccepted, CensusRefused, - discovery_census, census_planned_labels, + discovery_census, census_planned_labels, claim_route_universe, site_label_text, RequiredAggregateDerivation, RequiredAggregateDerived, RequiredAggregateUnderivable, required_aggregate_from_census, } @@ -714,7 +716,7 @@ fn native_universe() -> extdeps.bazel.target_pattern.TargetPattern { // proves the set-form admission changed nothing for the operand the verb was born serving. Without // the second half, an admission that answered ExactTarget for every text would pass. test fn an_exact_label_is_admitted_as_the_single_target_and_still_routes() -> Bool { - match admit_test_operand(operand: differential_operand(), native_universe: native_universe()) { + match admit_test_operand(operand: differential_operand(), native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandExactTarget { label: l } => label_eq(a: l, b: heads_reading_differential_label()) && (match invoke_target(operand: differential_operand()) { @@ -724,6 +726,7 @@ test fn an_exact_label_is_admitted_as_the_single_target_and_still_routes() -> Bo }) TestOperandSetForm { pattern: _ } => false TestOperandNativeRoute { pattern: _ } => false + TestOperandClaimRoute { pattern: _ } => false TestOperandRefused { operand: _, cause: _ } => false } } @@ -732,11 +735,12 @@ test fn an_exact_label_is_admitted_as_the_single_target_and_still_routes() -> Bo // not merely the arm — is what discriminates against an admission that parsed `//pkg/...` as a // package-wide pattern: the two arms render differently. test fn a_subtree_pattern_is_admitted_as_a_set_form() -> Bool { - match admit_test_operand(operand: "//dag/test/claim/compute/...", native_universe: native_universe()) { + match admit_test_operand(operand: "//dag/test/claim/compute/...", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandSetForm { pattern: p } => render_target_pattern(pattern: p) == "//dag/test/claim/compute/..." TestOperandExactTarget { label: _ } => false TestOperandNativeRoute { pattern: _ } => false + TestOperandClaimRoute { pattern: _ } => false TestOperandRefused { operand: _, cause: _ } => false } } @@ -745,17 +749,19 @@ test fn a_subtree_pattern_is_admitted_as_a_set_form() -> Bool { // authority treats them alike, so the admission must not invent a third arm for the second // spelling. test fn package_patterns_are_admitted_as_set_forms() -> Bool { - match admit_test_operand(operand: "//dag/test/claim/compute:all", native_universe: native_universe()) { + match admit_test_operand(operand: "//dag/test/claim/compute:all", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandSetForm { pattern: p } => render_target_pattern(pattern: p) == "//dag/test/claim/compute:all" TestOperandExactTarget { label: _ } => false TestOperandNativeRoute { pattern: _ } => false + TestOperandClaimRoute { pattern: _ } => false TestOperandRefused { operand: _, cause: _ } => false - } && match admit_test_operand(operand: "//dag/test/claim/compute:*", native_universe: native_universe()) { + } && match admit_test_operand(operand: "//dag/test/claim/compute:*", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandSetForm { pattern: p } => render_target_pattern(pattern: p) == "//dag/test/claim/compute:all" TestOperandExactTarget { label: _ } => false TestOperandNativeRoute { pattern: _ } => false + TestOperandClaimRoute { pattern: _ } => false TestOperandRefused { operand: _, cause: _ } => false } } @@ -765,15 +771,15 @@ test fn package_patterns_are_admitted_as_set_forms() -> Bool { // the label grammar refuses carries that refusal INSIDE `PatternLabelRefused`, which is what // keeps "the pattern layer refuses" and "the label layer refuses" from collapsing into one bit. test fn refused_operand_forms_carry_the_pattern_authoritys_typed_causes() -> Bool { - (match admit_test_operand(operand: "dag/test/claim/compute/...", native_universe: native_universe()) { + (match admit_test_operand(operand: "dag/test/claim/compute/...", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandRefused { operand: _, cause: c } => match c { PatternNotAbsolute { text: t } => t == "dag/test/claim/compute/..." _ => false } _ => false - }) && (match admit_test_operand(operand: "//dag/test/claim:all-targets", native_universe: native_universe()) { + }) && (match admit_test_operand(operand: "//dag/test/claim:all-targets", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandRefused { operand: _, cause: c } => match c { PatternAllTargetsUnsupported { text: t } => t == "//dag/test/claim:all-targets" _ => false } _ => false - }) && (match admit_test_operand(operand: "//dag/test/claim:bad/name", native_universe: native_universe()) { + }) && (match admit_test_operand(operand: "//dag/test/claim:bad/name", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandRefused { operand: _, cause: c } => match c { PatternLabelRefused { cause: _ } => true _ => false } _ => false @@ -783,7 +789,7 @@ test fn refused_operand_forms_carry_the_pattern_authoritys_typed_causes() -> Boo // AND THE REFUSAL RENDERS FOR A HUMAN. A typed cause nothing renders is a cause that exists for // programs only; the text names the operand and the authority's own refusal text. test fn a_refused_operand_renders_the_operand_and_the_authoritys_cause() -> Bool { - match admit_test_operand(operand: "dag/test/claim/compute/...", native_universe: native_universe()) { + match admit_test_operand(operand: "dag/test/claim/compute/...", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandRefused { operand: o, cause: c } => string_contains(s: test_operand_refusal_rendered(operand: o, cause: c), pattern: "dag/test/claim/compute/...") && string_contains(s: test_operand_refusal_rendered(operand: o, cause: c), pattern: "not absolute") @@ -791,15 +797,106 @@ test fn a_refused_operand_renders_the_operand_and_the_authoritys_cause() -> Bool } } -// A SET FORM IS ADMITTED AND THEN REFUSED BY THE ROUTE, NEVER DELEGATED. The rendering names the -// operand, says the form is a SET, and says it is not handed to the interpreter -- a text that -// dropped the last clause would let a reader assume the interpreted route still answers. -test fn a_set_form_refusal_names_the_native_route_and_refuses_delegation() -> Bool { +// A SET FORM OUTSIDE EVERY UNIVERSE IS ADMITTED AND THEN REFUSED BY THE ROUTE, NEVER WIDENED. The +// rendering names the operand, says the form is a SET, names BOTH routes whose universes it lies +// outside, and says it is not widened into another route -- a text that dropped the claim route +// would send a reader looking for an executor that does exist, under the spelling it answers to. +test fn a_set_form_refusal_names_both_routes_and_refuses_widening() -> Bool { let text = test_operand_set_form_refusal_rendered(operand: "//dag/test/claim/compute/...") string_contains(s: text, pattern: "//dag/test/claim/compute/...") && string_contains(s: text, pattern: "SET") && string_contains(s: text, pattern: "native test route") - && string_contains(s: text, pattern: "never delegated to the interpreter") + && string_contains(s: text, pattern: "claim route") + && string_contains(s: text, pattern: "never widened into another route") +} + +// THE CLAIM ROUTE'S UNIVERSE, SUPPLIED HERE for the same reason `native_universe` is: the arms +// below discriminate the containment decision, not one compiled-in constant. The production value +// is `gunbc.discovery_census` `claim_route_universe`, and +// `the_production_claim_universe_is_the_site_label_subtree` pins that it is the subtree the +// census's own labels occupy. +fn claim_universe() -> extdeps.bazel.target_pattern.TargetPattern { + SubtreeTargets { package: PackagePath { segments: ["test" as NonEmptyStr, "claim" as NonEmptyStr] } } +} + +fn admits_to_claim_route_as(operand: String, rendered: String) -> Bool { + match admit_test_operand(operand: operand, native_universe: native_universe(), claim_universe: claim_universe()) { + TestOperandClaimRoute { pattern: p } => render_target_pattern(pattern: p) == rendered + TestOperandNativeRoute { pattern: _ } => false + TestOperandExactTarget { label: _ } => false + TestOperandSetForm { pattern: _ } => false + TestOperandRefused { operand: _, cause: _ } => false + } +} + +// AN EXACT CLAIM LABEL IS THE CLAIM ROUTE'S OPERAND, NOT A REGISTRY LOOKUP. Before the claim route +// existed this operand reached the instrument registry and came back `no such target`. The render +// assertion discriminates against an admission that admitted the arm but rewrote the pattern. +test fn an_exact_claim_label_admits_to_the_claim_route() -> Bool { + admits_to_claim_route_as( + operand: "//test/claim/target_invocation_witness:the_universe_contains_itself_and_not_its_siblings", + rendered: "//test/claim/target_invocation_witness:the_universe_contains_itself_and_not_its_siblings" + ) +} + +// A MODULE'S `:all` AND `:*` AND A `/...` SUBTREE ARE CLAIM-ROUTE SET FORMS -- the population the +// set-form refusal used to say no executor enumerates. `:*` renders canonically as `:all`. +test fn claim_set_forms_admit_to_the_claim_route() -> Bool { + admits_to_claim_route_as( + operand: "//test/claim/target_invocation_witness:all", + rendered: "//test/claim/target_invocation_witness:all" + ) + && admits_to_claim_route_as( + operand: "//test/claim/target_invocation_witness:*", + rendered: "//test/claim/target_invocation_witness:all" + ) + && admits_to_claim_route_as(operand: "//test/claim/...", rendered: "//test/claim/...") + && admits_to_claim_route_as(operand: "//test/claim/long/...", rendered: "//test/claim/long/...") +} + +// OUTSIDE EVERY UNIVERSE STILL REFUSES, AND THE NATIVE UNIVERSE STILL WINS. `//test/...` CONTAINS +// the claim universe rather than lying inside it, so it is a set form with no executor; an +// instrument label is still the exact route; and a native operand never reaches the claim route. +// Without these an admission that routed EVERYTHING to the claim executor would pass the arms above. +test fn operands_outside_the_claim_universe_do_not_admit_to_it() -> Bool { + (match admit_test_operand(operand: "//test/...", native_universe: native_universe(), claim_universe: claim_universe()) { + TestOperandSetForm { pattern: _ } => true + _ => false + }) && (match admit_test_operand(operand: "//dag/test/claim/compute:all", native_universe: native_universe(), claim_universe: claim_universe()) { + TestOperandSetForm { pattern: _ } => true + _ => false + }) && (match admit_test_operand(operand: differential_operand(), native_universe: native_universe(), claim_universe: claim_universe()) { + TestOperandExactTarget { label: _ } => true + _ => false + }) && (match admit_test_operand(operand: "//v2/test/parse:all", native_universe: native_universe(), claim_universe: claim_universe()) { + TestOperandNativeRoute { pattern: _ } => true + _ => false + }) +} + +// THE PRODUCTION UNIVERSE IS THE SUBTREE THE CENSUS'S OWN LABELS OCCUPY. A claim site labelled by +// `site_label_text` must lie inside `claim_route_universe`, or the route would refuse the very +// labels the census mints; and the universe must not reach the native one. +test fn the_production_claim_universe_is_the_site_label_subtree() -> Bool { + let site = DiscoveredSite { + module_path: "test.claim.target_invocation_witness", + function: "w", + reads_live_tree: SubstrateInputsOnly + } + render_target_pattern(pattern: claim_route_universe()) == "//test/claim/..." + && admits_to_claim_route_as(operand: site_label_text(site: site), rendered: "//test/claim/target_invocation_witness:w") + && !target_pattern_within(inner: claim_route_universe(), outer: native_universe()) +} + +// THE CLAIM ROUTE'S TERMINATION AND ITS EXIT STATUS. Each arm is pinned through +// `invocation_exit_status`, so a fold that answered held for an empty selection, or let an +// unobserved member mask a definite failure, or reported an unobserved one as a failure, reds here. +test fn the_claim_route_termination_maps_to_the_three_exit_statuses() -> Bool { + invocation_exit_status(t: claim_route_termination(verdicts: [ClaimHeld {}, ClaimHeld {}])) == 0 + && invocation_exit_status(t: claim_route_termination(verdicts: [ClaimHeld {}, ClaimDidNotHold {}])) == 1 + && invocation_exit_status(t: claim_route_termination(verdicts: [ClaimUnobserved {}, ClaimDidNotHold {}])) == 1 + && invocation_exit_status(t: claim_route_termination(verdicts: [ClaimHeld {}, ClaimUnobserved {}])) == 2 + && invocation_exit_status(t: claim_route_termination(verdicts: [])) == 2 } // THE CONTAINMENT DECIDES THE EXECUTOR, AND BOTH DIRECTIONS ARE PINNED. @@ -809,15 +906,17 @@ test fn a_set_form_refusal_names_the_native_route_and_refuses_delegation() -> Bo // it still has no executor. One claim asserting only the first would pass for an admission that // routed EVERYTHING native; one asserting only the second would pass for the pre-change behaviour. test fn a_pattern_inside_the_native_universe_routes_to_the_native_route() -> Bool { - (match admit_test_operand(operand: "//v2/test/parse:all", native_universe: native_universe()) { + (match admit_test_operand(operand: "//v2/test/parse:all", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandNativeRoute { pattern: p } => render_target_pattern(pattern: p) == "//v2/test/parse:all" TestOperandSetForm { pattern: _ } => false TestOperandExactTarget { label: _ } => false TestOperandRefused { operand: _, cause: _ } => false - }) && (match admit_test_operand(operand: "//dag/test/claim/compute:all", native_universe: native_universe()) { + TestOperandClaimRoute { pattern: _ } => false + }) && (match admit_test_operand(operand: "//dag/test/claim/compute:all", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandSetForm { pattern: _ } => true TestOperandNativeRoute { pattern: _ } => false + TestOperandClaimRoute { pattern: _ } => false TestOperandExactTarget { label: _ } => false TestOperandRefused { operand: _, cause: _ } => false }) @@ -830,13 +929,15 @@ test fn a_pattern_inside_the_native_universe_routes_to_the_native_route() -> Boo test fn a_single_target_inside_the_native_universe_routes_to_the_native_route() -> Bool { match admit_test_operand( operand: "//v2/test/parse:expression_bodied_fn_decl_parse", - native_universe: native_universe() + native_universe: native_universe(), + claim_universe: claim_universe() ) { TestOperandNativeRoute { pattern: p } => render_target_pattern(pattern: p) == "//v2/test/parse:expression_bodied_fn_decl_parse" TestOperandExactTarget { label: _ } => false TestOperandSetForm { pattern: _ } => false TestOperandRefused { operand: _, cause: _ } => false + TestOperandClaimRoute { pattern: _ } => false } } @@ -1132,7 +1233,7 @@ test fn the_bare_channel_standing_rendering_reports_the_read_populations() -> Bo // grammar refuses renders the pattern layer's sentence AND the label layer's located cause, so a // renderer that dropped the inner cause reds here even though the typed arm above still holds. test fn a_refused_single_target_renders_the_label_grammars_located_cause() -> Bool { - match admit_test_operand(operand: "//dag/test/claim:bad/name", native_universe: native_universe()) { + match admit_test_operand(operand: "//dag/test/claim:bad/name", native_universe: native_universe(), claim_universe: claim_universe()) { TestOperandRefused { operand: o, cause: c } => string_contains(s: test_operand_refusal_rendered(operand: o, cause: c), pattern: "label grammar refuses") && string_contains(s: test_operand_refusal_rendered(operand: o, cause: c), pattern: "contains `/`") diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 52f74026a78..996a4da9a8e 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -24865,6 +24865,101 @@ pub fn render_selected_entry_closure_overlap_json(m: &SelectedEntryClosureOverla out } +/// One selected claim and what its evaluation observed. `module_path` is the entry's AUTHORED +/// module name (read off the `module` line by the module index), which is what the claim's label +/// is minted from (`gunbc.discovery_census` `site_label`). +pub struct ClaimRouteMember { + pub module_path: String, + pub function: String, + pub outcome: ClaimOutcome, + pub eval_steps: u64, + pub cpu_ms: u128, +} + +/// THE CLAIM ROUTE'S EXECUTOR: the floor's discovery authority over the modules the operand can +/// reach, then the floor's claim evaluation over the claims it selects. +/// +/// NOTHING HERE DECIDES WHAT A CLAIM IS. Which `test` declarations a source enrolls is +/// `floor_discovery_rows_over_sources` -- the same per-file authority fold the required floor runs, +/// over a narrower subject -- and whether a claim held is `run_claim_measured`, the evaluation +/// `claim_batch` and the floor share. The caller supplies two predicates: `module_selected` +/// narrows the SUBJECT before discovery (an operand naming one module never pays for a corpus +/// walk), and `claim_selected` narrows discovery's rows to the operand's own population. +/// +/// One context per ENTRY, as `claim_batch` builds it: every claim of a module shares that module's +/// resolved graph, and the eval-call memo is released at each claim's frame exit. Floor ADMISSION +/// policy -- gate prefixes, prepared-subject exclusions, eval-step budget tiers -- is deliberately +/// not applied: that is the `//:required` aggregate's question, and this route answers what a +/// named claim OBSERVED, the distinction `gunbc.target_invocation` keeps for every direct +/// invocation. +pub fn run_claim_route( + source_roots: &[String], + module_selected: &dyn Fn(&str) -> bool, + claim_selected: &dyn Fn(&str, &str) -> bool, +) -> Result, String> { + let index = process_shared_index(source_roots); + let mut selected: Vec<(&String, &Rc)> = index + .source_files + .iter() + .filter(|(module_path, _)| module_selected(module_path)) + .collect(); + if selected.is_empty() { + return Ok(Vec::new()); + } + selected.sort_by(|a, b| a.0.cmp(b.0)); + let module_for_path: std::collections::HashMap = selected + .iter() + .map(|(m, sf)| (sf.path.replace('\\', "/"), (*m).clone())) + .collect(); + let (graph, indices) = resolve_workspace_entry(source_roots, FLOOR_DISCOVERY_PRODUCER_ENTRY) + .map_err(|e| format!("floor discovery authority resolve: {e}"))?; + let frame = make_eval_context(&graph, indices, v1_interpreter::ExecutionMode::Wet); + let rows = + floor_discovery_rows_over_sources(&frame, selected.iter().map(|(_, sf)| sf.as_ref())) + .map_err(|refusal| refusal.rendered())?; + let mut groups: Vec<(String, String, Vec)> = Vec::new(); + for row in rows { + let Some(module_path) = module_for_path.get(row.entry.as_str()) else { + return Err(format!( + "cause=FloorDiscoveryEntryOutsideSubject entry={} — the discovery authority \ + enrolled an entry the selected subject does not hold", + row.entry + )); + }; + if !claim_selected(module_path, &row.function) { + continue; + } + match groups.last_mut() { + Some((entry, _, functions)) if *entry == row.entry => functions.push(row.function), + _ => groups.push((row.entry, module_path.clone(), vec![row.function])), + } + } + let mut members = Vec::new(); + for (entry, module_path, functions) in groups { + let (graph, source_indices) = resolve_entry_with_index(&index, &entry) + .map_err(|e| format!("resolve {entry}: {e}"))?; + let closure_subject = closure_subject_for_entry(&index, &entry) + .map_err(|e| format!("closure subject {entry}: {e}"))?; + let ctx = make_eval_context( + &graph, + source_indices, + v1_interpreter::ExecutionMode::Hermetic, + ); + for function in functions { + let (outcome, receipt) = run_claim_measured(&ctx, &closure_subject, &function); + v1_interpreter::eval_call_memo_frame_exit(&ctx); + members.push(ClaimRouteMember { + module_path: module_path.clone(), + function, + outcome, + eval_steps: receipt.eval_steps, + cpu_ms: receipt.cpu_nanos / 1_000_000, + }); + } + } + Ok(members) +} + pub fn discover_floor_witness_roster( source_roots: &[String], scan_dirs: &[String], diff --git a/src/v1/stage0/src/cli_run/required_floor_runner.rs b/src/v1/stage0/src/cli_run/required_floor_runner.rs index beba35ece6e..e1ba5012f5e 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -569,6 +569,93 @@ pub(crate) fn run_claim_evaluation( .map_err(|payload| panic_payload_text(&payload)) } +/// Why the discovery authority's per-file fold produced no roster. Two causes, two repairs: the +/// authority could not be EVALUATED (a frame or interpreter defect, located at the source when one +/// was being folded), or it evaluated and REFUSED the corpus (a placement or sidecar violation, +/// carried as the producer's own reason). +pub(crate) enum FloorDiscoveryFoldRefusal { + Unevaluable { + source: Option, + function: &'static str, + error: String, + }, + Refused { + reason: String, + }, +} + +impl FloorDiscoveryFoldRefusal { + pub(crate) fn rendered(&self) -> String { + match self { + FloorDiscoveryFoldRefusal::Unevaluable { + source: Some(source), + function, + error, + } => format!( + "cause=FloorDiscoveryAuthorityUnevaluable source={source} — {function}: {error}" + ), + FloorDiscoveryFoldRefusal::Unevaluable { + source: None, + function, + error, + } => format!("cause=FloorDiscoveryAuthorityUnevaluable — {function}: {error}"), + FloorDiscoveryFoldRefusal::Refused { reason } => { + format!("cause=FloorDiscoveryRefused — {reason}") + } + } + } +} + +/// THE FLOOR'S DISCOVERY, AS ONE FOLD OVER WHATEVER SOURCES THE CALLER HOLDS. +/// +/// `v2.workflow.floor_discovery_source_authority` `discover_floor_rows_for_source` per source, then +/// `floor_discovery_finalize_source_outcomes` over the outcomes -- the one authority for which +/// `test` declarations a source enrolls. The required floor folds it over its full inventory; +/// `gunbc test`'s claim route folds it over the modules its operand names. Same authority, same +/// calls, narrower subject: the subject narrows, the decision does not move (review 44641's rule). +/// `frame` must have that module in scope by its qualified name. +pub(crate) fn floor_discovery_rows_over_sources<'a>( + frame: &v1_interpreter::InterpContext, + sources: impl IntoIterator, +) -> Result, FloorDiscoveryFoldRefusal> { + let mut outcomes: Vec = Vec::new(); + for source in sources { + let repo_path = source.path.replace('\\', "/"); + let args = [ + (Some("repo_path".to_string()), str_value(repo_path.clone())), + ( + Some("content".to_string()), + str_value(source.content.clone()), + ), + ]; + let outcome = v1_interpreter::run_in_context_with_args( + frame, + "v2.workflow.floor_discovery_source_authority.discover_floor_rows_for_source", + &args, + false, + ) + .map_err(|e| FloorDiscoveryFoldRefusal::Unevaluable { + source: Some(repo_path), + function: "discover_floor_rows_for_source", + error: e.to_string(), + })?; + outcomes.push(outcome); + } + let finalized = v1_interpreter::run_in_context_with_args( + frame, + "v2.workflow.floor_discovery_source_authority.floor_discovery_finalize_source_outcomes", + &[(Some("outcomes".to_string()), list_value_from_vec(outcomes))], + false, + ) + .map_err(|e| FloorDiscoveryFoldRefusal::Unevaluable { + source: None, + function: "floor_discovery_finalize_source_outcomes", + error: e.to_string(), + })?; + parse_floor_discovery_producer_result(frame, &finalized) + .map_err(|reason| FloorDiscoveryFoldRefusal::Refused { reason }) +} + /// Read the grounded opaque-host-call surface, or refuse. /// /// RETURNS THE OPERATIONS, NEVER AN EMPTY VEC ON FAILURE. `opaque_host_call_surface()` answers a @@ -9465,53 +9552,11 @@ pub fn run_required_floor( let discovery_started = std::time::Instant::now(); let producer_frame = floor_authority_frame(&prepared, FLOOR_DISCOVERY_AUTHORITY_MODULE)?; let discovery_source_count = full_inventory.len(); - let mut discovery_outcomes: Vec = - Vec::with_capacity(full_inventory.len()); - for src in &full_inventory { - let args = [ - ( - Some("repo_path".to_string()), - str_value(src.source.path.replace('\\', "/")), - ), - ( - Some("content".to_string()), - str_value(src.source.content.clone()), - ), - ]; - let outcome = v1_interpreter::run_in_context_with_args( - &producer_frame, - "v2.workflow.floor_discovery_source_authority.discover_floor_rows_for_source", - &args, - false, - ) - .map_err(|e| { - format!( - "REQUIRED-FLOOR REFUSAL cause=FloorDiscoveryAuthorityUnevaluable source={} — \ - discover_floor_rows_for_source: {e}", - src.source.path.replace('\\', "/") - ) - })?; - discovery_outcomes.push(outcome); - } - let finalized = v1_interpreter::run_in_context_with_args( + let discovery_rows = floor_discovery_rows_over_sources( &producer_frame, - "v2.workflow.floor_discovery_source_authority.floor_discovery_finalize_source_outcomes", - &[( - Some("outcomes".to_string()), - list_value_from_vec(discovery_outcomes), - )], - false, + full_inventory.iter().map(|src| src.source.as_ref()), ) - .map_err(|e| { - format!( - "REQUIRED-FLOOR REFUSAL cause=FloorDiscoveryAuthorityUnevaluable — \ - floor_discovery_finalize_source_outcomes: {e}" - ) - })?; - let discovery_rows = parse_floor_discovery_producer_result(&producer_frame, &finalized) - .map_err(|reason| { - format!("REQUIRED-FLOOR REFUSAL cause=FloorDiscoveryRefused — {reason}") - })?; + .map_err(|refusal| format!("REQUIRED-FLOOR REFUSAL {}", refusal.rendered()))?; // Rows are (entry, function); the disposition loop below needs the entry's AUTHORED module // name, which preparation read off the `module` line and holds beside the same path. let module_for_path: std::collections::HashMap = full_inventory diff --git a/src/v1/stage0/src/main.rs b/src/v1/stage0/src/main.rs index 7de39dbb2a0..afe2583a7e3 100644 --- a/src/v1/stage0/src/main.rs +++ b/src/v1/stage0/src/main.rs @@ -786,8 +786,9 @@ fn retained_dispatch(command: RetainedCommands, dry_run: bool) -> ! { // decided by the registry in `target_invocation_host`, mirroring // `gunbc.instrument_targets`, and the realization is selected one level below. A second // instrument adds a row there and nothing here. A set PATTERN (`//pkg:all`, `//pkg/...`) - // is admitted by the same host's `parse_target_pattern` mirror and refused with status 2 - // until the native test route executes it — never delegated to the interpreter. + // is admitted by the same host's `parse_target_pattern` mirror and executed by the route + // whose universe contains it -- the native test route (`//v2/test/...`) or the claim + // route (`//test/claim/...`); outside both it is refused with status 2, never widened. // // The status is the producer's own termination, not an aggregate verdict: 0 the reading // held, 1 it did not, 2 no reading was taken. `gunbc.build_target`'s diff --git a/src/v1/stage0/src/target_invocation_host.rs b/src/v1/stage0/src/target_invocation_host.rs index 471a8e24acf..e51e9318899 100644 --- a/src/v1/stage0/src/target_invocation_host.rs +++ b/src/v1/stage0/src/target_invocation_host.rs @@ -5,10 +5,11 @@ // termination vocabulary), `gunbc.instrument_targets` (live target and binding rows, the // differential's classifier and rendering), `extdeps.bazel.label` (the label grammar mirrored // here) and `extdeps.bazel.target_pattern` (the pattern grammar mirrored here). The SET forms -// (`//pkg:all`, `//pkg:*`, `//pkg/...`) this file admits are NOT executed anywhere yet: they are -// refused with status 2 (`test_operand_set_form_refusal_rendered`, mirroring -// `gunbc.target_invocation`), because their only admissible executor is the native test route and -// no interpreter delegation may stand in for it. None of the +// (`//pkg:all`, `//pkg:*`, `//pkg/...`) execute only inside a route's universe: the native test +// route (`//v2/test/...`) or the claim route (`//test/claim/...`, the discovered witness claims, +// evaluated by the floor's own discovery authority and claim evaluation). A set form outside both +// is refused with status 2 (`test_operand_set_form_refusal_rendered`, mirroring +// `gunbc.target_invocation`) and is never widened into another route. None of the // modeled modules is in the v1 seed's emitted closure — `src/gunbc_cli_dispatch_surface.rs` is // the only `gunbc.*` mirror the emitter produces — so this file is hand-written beside the // carrier, as `required_regen_host.rs` mirrors `v2.workflow.required_regen`. The seam is @@ -17,8 +18,9 @@ // `gunbc.target_invocation_seed_growth`. // // WHAT IS AND IS NOT GENERIC HERE. One route: argv operand -> admit pattern -> the operand's -// CONTAINMENT in the native route's universe decides the executor. Inside it, the native test -// route adjudicates that pattern through the emitted compiler. Outside it, a single target builds +// CONTAINMENT in a route's universe decides the executor. Inside the native one, the native test +// route adjudicates that pattern through the emitted compiler; inside the claim one, the claim +// route evaluates the discovered claims the pattern selects. Outside both, a single target builds // the registry, exact lookup, invoke the bound producer, render its native standing; a set form // has no executor and is refused with status 2. No per-instrument arm on that route, and none // may be added; a second instrument is a row in `instrument_registry` plus one `Producer` arm in @@ -2064,10 +2066,133 @@ fn native_member_outcome( /// `test_operand_set_form_refusal_rendered`. fn test_operand_set_form_refusal_rendered(operand: &str) -> String { format!( - "gunbc test: {operand} denotes a SET of targets outside the native test route's universe; no executor enumerates that population, and set forms are never delegated to the interpreter" + "gunbc test: {operand} denotes a SET of targets outside every route's universe (the native test route and the claim route); no executor enumerates that population, and a set form is never widened into another route" ) } +/// `gunbc.discovery_census` `claim_route_universe`, mirrored: `//test/claim/...`, the subtree +/// `site_label` maps every `test.claim.*` claim into. An operand contained in it (and not in the +/// native universe, which is asked first) belongs to the claim route. +pub fn claim_route_universe() -> TargetPattern { + TargetPattern::SubtreeTargets(vec!["test".to_string(), "claim".to_string()]) +} + +/// `gunbc.target_invocation` `ClaimRouteVerdict`, mirrored. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ClaimRouteVerdict { + ClaimHeld, + ClaimDidNotHold, + ClaimUnobserved, +} + +/// One claim's outcome as the route's verdict. `Fail`, `NotBool` and `ExitFailure` are +/// observations that did not hold; every other arm is the absence of an observation -- an error, +/// a refusal, an unwind, a budget interrupt -- and is never reported as a failing claim. +/// Wildcard-free so a new outcome arm must be classified here. +fn claim_route_verdict(outcome: &cli_run::ClaimOutcome) -> ClaimRouteVerdict { + use cli_run::ClaimOutcome as O; + match outcome { + O::Pass => ClaimRouteVerdict::ClaimHeld, + O::Fail | O::NotBool { .. } | O::ExitFailure { .. } => ClaimRouteVerdict::ClaimDidNotHold, + O::RuntimeError { .. } + | O::BudgetInterrupted { .. } + | O::CompletedOverBudget { .. } + | O::HostToolUnresolved { .. } + | O::HostEffectRefused { .. } + | O::Panicked { .. } + | O::NotAttempted { .. } => ClaimRouteVerdict::ClaimUnobserved, + } +} + +/// `gunbc.target_invocation` `claim_route_termination`, mirrored: a definite failure dominates, an +/// unobserved member or an empty selection is `SubjectUnreached`. +fn claim_route_termination(verdicts: &[ClaimRouteVerdict]) -> Termination { + if verdicts.contains(&ClaimRouteVerdict::ClaimDidNotHold) { + Termination::ObservationDidNotHold + } else if verdicts.contains(&ClaimRouteVerdict::ClaimUnobserved) || verdicts.is_empty() { + Termination::SubjectUnreached + } else { + Termination::ObservationHeld + } +} + +/// THE CLAIM ROUTE: select the discovered claims the operand's pattern contains, evaluate each, +/// print one line per claim, fold the verdicts. The subject is narrowed to the modules the pattern +/// can reach BEFORE discovery, so `//test/claim/m:x` reads one module, not the corpus; the corpus +/// roots are `cli_run::witness_layer_roots`, the floor's own authority for them. +fn run_claim_route(pattern: &TargetPattern) -> InvocationOutcome { + let rendered = render_target_pattern(pattern); + let module_selected = |module_path: &str| { + let segments: Vec = module_path.split('.').map(str::to_string).collect(); + match pattern { + TargetPattern::SingleTarget(label) => label.package_segments == segments, + TargetPattern::PackageTargets(_) | TargetPattern::SubtreeTargets(_) => { + target_pattern_within(&TargetPattern::PackageTargets(segments), pattern) + } + } + }; + let claim_selected = |module_path: &str, function: &str| { + let label = Label { + package_segments: module_path.split('.').map(str::to_string).collect(), + target: function.to_string(), + }; + target_pattern_within(&TargetPattern::SingleTarget(label), pattern) + }; + let members = match cli_run::run_claim_route( + &cli_run::witness_layer_roots(), + &module_selected, + &claim_selected, + ) { + Ok(members) => members, + Err(cause) => { + return InvocationOutcome { + termination: Termination::SubjectUnreached, + message: format!("claim route: {rendered} — {cause}"), + } + } + }; + let mut verdicts = Vec::with_capacity(members.len()); + for m in &members { + let verdict = claim_route_verdict(&m.outcome); + let word = match verdict { + ClaimRouteVerdict::ClaimHeld => "HELD", + ClaimRouteVerdict::ClaimDidNotHold => "DID-NOT-HOLD", + ClaimRouteVerdict::ClaimUnobserved => "UNOBSERVED", + }; + let label = render_label(&Label { + package_segments: m.module_path.split('.').map(str::to_string).collect(), + target: m.function.clone(), + }); + let detail = match verdict { + ClaimRouteVerdict::ClaimHeld => String::new(), + _ => format!(" outcome={:?}", m.outcome), + }; + println!( + "{label} {word} eval_steps={} cpu_ms={}{detail}", + m.eval_steps, m.cpu_ms + ); + verdicts.push(verdict); + } + let count = |v: ClaimRouteVerdict| verdicts.iter().filter(|x| **x == v).count(); + let held = count(ClaimRouteVerdict::ClaimHeld); + let did_not_hold = count(ClaimRouteVerdict::ClaimDidNotHold); + let unobserved = count(ClaimRouteVerdict::ClaimUnobserved); + let termination = claim_route_termination(&verdicts); + let note = if verdicts.is_empty() { + " — the pattern selected no discovered claim" + } else { + "" + }; + InvocationOutcome { + termination, + message: format!( + "claim route: {rendered} — {} claim(s): {held} held, {did_not_hold} did not hold, \ + {unobserved} unobserved{note}", + verdicts.len() + ), + } +} + /// `gunbc.target_invocation` `BinaryInput`, mirrored. #[derive(Debug, Clone, PartialEq, Eq)] pub enum BinaryInput { @@ -2415,6 +2540,9 @@ pub fn test_verb(operand: &str) -> InvocationOutcome { if target_pattern_within(&pattern, &native_route_default_pattern()) { return run_native_test_route(&pattern); } + if target_pattern_within(&pattern, &claim_route_universe()) { + return run_claim_route(&pattern); + } let label = match pattern { TargetPattern::SingleTarget(label) => label, TargetPattern::PackageTargets(_) | TargetPattern::SubtreeTargets(_) => { @@ -3219,6 +3347,38 @@ fn run_floor_memory_qualification() -> InvocationOutcome { mod native_route_termination_tests { use super::*; + /// `gunbc.target_invocation` `claim_route_termination`, mirrored: the same five rows + /// `test.claim.target_invocation_witness` `the_claim_route_termination_maps_to_the_three_exit_statuses` + /// pins on the model, so the host mirror cannot drift from it silently. + #[test] + fn claim_route_termination_matches_the_model() { + use ClaimRouteVerdict::*; + let status = |v: &[ClaimRouteVerdict]| invocation_exit_status(claim_route_termination(v)); + assert_eq!(status(&[ClaimHeld, ClaimHeld]), 0); + assert_eq!(status(&[ClaimHeld, ClaimDidNotHold]), 1); + assert_eq!(status(&[ClaimUnobserved, ClaimDidNotHold]), 1); + assert_eq!(status(&[ClaimHeld, ClaimUnobserved]), 2); + assert_eq!(status(&[]), 2); + } + + /// The claim universe is asked AFTER the native one and refuses what lies outside it, mirroring + /// the model's admission rows. + #[test] + fn claim_universe_containment_matches_the_model() { + let within = |operand: &str| { + target_pattern_within( + &parse_target_pattern(operand).expect("pattern"), + &claim_route_universe(), + ) + }; + assert!(within("//test/claim/target_invocation_witness:w")); + assert!(within("//test/claim/target_invocation_witness:all")); + assert!(within("//test/claim/...")); + assert!(!within("//test/...")); + assert!(!within("//dag/test/claim/compute:all")); + assert!(!within("//gunbc/instruments:self-host")); + } + /// `gunbc.instrument_targets` `native_route_termination_under_qualification`, mirrored here and /// given its own red. The `.dag` witness pins the fold; this pins that THIS host applies it. ///