diff --git a/.gitattributes b/.gitattributes index e7c5610cf1b..4983bd81d0a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -144,6 +144,7 @@ src/v1/stage0/src/std_pareto.rs merge=generated-artifact src/v1/stage0/src/std_process_termination.rs merge=generated-artifact src/v1/stage0/src/std_realization_schedule.rs merge=generated-artifact src/v1/stage0/src/std_reference_binding_observation.rs merge=generated-artifact +src/v1/stage0/src/std_reference_derived_closure.rs merge=generated-artifact src/v1/stage0/src/std_roster_frontier.rs merge=generated-artifact src/v1/stage0/src/std_serialization.rs merge=generated-artifact src/v1/stage0/src/std_source_annotation.rs merge=generated-artifact @@ -205,11 +206,15 @@ src/v1/stage0/src/v1_std_core.rs merge=generated-artifact src/v1/stage0/src/v1_test_non_ascii_perf_fixture.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_caret_parse_smoke_test.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_dag_parse_continuation_operator_witness_test.rs merge=generated-artifact +src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_namespace_reference_derived_closure_production_admissions_witness_test.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_occurrence_identity_debt_receipt_test.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_ordinary_frontend_observation_test.rs merge=generated-artifact +src/v1/stage0/src/v1_tests_claim_parser_declaration_category_matrix_witness_test.rs merge=generated-artifact +src/v1/stage0/src/v1_tests_claim_parser_declaration_matrix_support.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_pattern_binder_declaration_node_test.rs merge=generated-artifact +src/v1/stage0/src/v1_tests_claim_reference_derived_closure_witness_test.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_required_expr_newline_continuation_test.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_v1_annotation_binding_test.rs merge=generated-artifact src/v1/stage0/src/v1_tests_claim_v1_annotation_capture_test.rs merge=generated-artifact diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2de92fcf0d..d6db4af09fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -416,7 +416,7 @@ jobs: git config user.email gunbc-ci-auto-heal@users.noreply.github.com if ! git merge --no-edit "$BUILT_FROM"; then CONFLICTS="$(git diff --name-only --diff-filter=U)" - AUTHORED_CONFLICTS="$(git diff --name-only --diff-filter=U -- . ':(exclude).github/workflows/ci.yml' ':(exclude).github/workflows/falsifier.yml' ':(exclude).github/workflows/fleet-converge.yml' ':(exclude).gitignore' ':(exclude).gitattributes' ':(exclude)ROADMAP.md' ':(exclude)DESIGN.md' ':(exclude).githooks/pre-push' ':(exclude).githooks/pre-commit' ':(exclude).githooks/generated-artifact-merge' ':(exclude)dag/gunbc/stage0_crate_layout_generated.dag' ':(exclude)src/v1/stage0/src/bootstrap_stage0_crate_layout_generated.rs' ':(exclude)dag/gunbc/stage0_crate_partition_generated.dag' ':(exclude)dag/gunbc/stage0_emit_plan_generated.dag' ':(exclude)src/v1/stage0/src/v1_interpreter_dispatch_generated.rs' ':(exclude)docs/plans/idea-machine.md' ':(exclude)docs/plans/algebraic-rewrite-optimization.md' ':(exclude)docs/plans/dag-v2-defork-audit.md' ':(exclude)docs/plans/v2-self-hosting.md' ':(exclude)docs/plans/m5-fixture-store-consolidation.md' ':(exclude)docs/plans/model-realization-fork.md' ':(exclude)docs/plans/value-null-split.md' ':(exclude)docs/plans/ci-merge-freshness.md' ':(exclude)docs/plans/seed-debt-bundle-item-2.md' ':(exclude)docs/plans/axiom-syllogism-lens.md' ':(exclude)docs/plans/invert-hand-maintained.md' ':(exclude)docs/plans/fail-closed-lockdown.md' ':(exclude)docs/plans/format-model-reconciliation.md' ':(exclude)docs/plans/regime2-shared-emission-fold.md' ':(exclude)docs/plans/shell-emission-model.md' ':(exclude)docs/plans/construction-justification-rule.md' ':(exclude)docs/plans/emission-ingestion-inverse.md' ':(exclude)docs/plans/testgen-oracle.md' ':(exclude)docs/plans/disposition-carrier.md' ':(exclude)docs/plans/fold-ergonomics.md' ':(exclude)docs/plans/m4-universal-hermetic-corpus.md' ':(exclude)docs/plans/wiring-liveness-preflight.md' ':(exclude)docs/plans/interpreter-kernel-d.md' ':(exclude)docs/plans/inert-layer-lens.md' ':(exclude)docs/plans/compile-clean-forcecheck.md' ':(exclude)docs/plans/expressibility-frontier.md' ':(exclude)docs/plans/p1-where-clause-lowering.md' ':(exclude)docs/plans/intent-linearity-design-draft.md' ':(exclude)docs/plans/solve-higher-order-design.md' ':(exclude)docs/plans/language-target-self-host-frontier.md' ':(exclude)docs/plans/self-applying-lenses.md' ':(exclude)docs/plans/budget-tree.md' ':(exclude)docs/plans/cardinality-refinement.md' ':(exclude)docs/plans/compute-envelope-model.md' ':(exclude)docs/plans/input-envelope-roadmap.md' ':(exclude)docs/plans/ci-selection-vs-scheduling.md' ':(exclude)docs/plans/ci-process-end-to-end.md' ':(exclude)docs/plans/ci-oom-reclassification.md' ':(exclude)docs/plans/bounded-input-cost-envelope-scheduling.md' ':(exclude)docs/plans/ci-humming.md' ':(exclude)docs/plans/edge-b-rust-dag-provenance-brief.md' ':(exclude)docs/plans/resolver-type-name-collision-wall.md' ':(exclude)docs/plans/realization-measurement-loop.md' ':(exclude)docs/plans/host-effect-orchestration.md' ':(exclude)docs/plans/ci-floor-fractal-gantt.md' ':(exclude)docs/plans/resolver-pathology-profile-receipt.md' ':(exclude)docs/plans/representation-minimization.md' ':(exclude)docs/plans/reference-grounding-migration.md' ':(exclude)docs/plans/typescript-gap-census.md' ':(exclude)docs/plans/cli-run-hollowing-plan.md' ':(exclude)docs/plans/fleet-acceptance-criteria.md' ':(exclude)docs/plans/structural-quadratic-wall-coverage-audit.md' ':(exclude)docs/plans/ci-performance-tanking-evidence.md' ':(exclude)docs/plans/extdeps-modeling-preflight.md' ':(exclude)docs/plans/discrete-cost-derivation.md' ':(exclude)docs/plans/v2-complexity-capability-parity.md' ':(exclude)src/v1/stage0/src/compiler_tests.rs' ':(exclude)src/v1/stage0/src/extdeps_cargo.rs' ':(exclude)src/v1/stage0/src/extdeps_cargo_version.rs' ':(exclude)src/v1/stage0/src/extdeps_container_oci_digest.rs' ':(exclude)src/v1/stage0/src/extdeps_external_authority.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_dag_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_dag_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_dag_types.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_go_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_go_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_go_types.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_python_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_python_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_python_types.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_rust_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_rust_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_rust_types.rs' ':(exclude)src/v1/stage0/src/extdeps_units_dimensionless.rs' ':(exclude)src/v1/stage0/src/extdeps_units_iec_80000_13.rs' ':(exclude)src/v1/stage0/src/extdeps_units_iso8601.rs' ':(exclude)src/v1/stage0/src/extdeps_units_iso_80000_3.rs' ':(exclude)src/v1/stage0/src/extdeps_uri.rs' ':(exclude)src/v1/stage0/src/extdeps_uri_path.rs' ':(exclude)src/v1/stage0/src/extdeps_version.rs' ':(exclude)src/v1/stage0/src/extdeps_version_semver.rs' ':(exclude)src/v1/stage0/src/gunbc_namespace_reference_derived_closure_admission.rs' ':(exclude)src/v1/stage0/src/gunbc_namespace_reference_derived_closure_contract.rs' ':(exclude)src/v1/stage0/src/gunbc_rust_decl_type_overlay.rs' ':(exclude)src/v1/stage0/src/gunbc_stage0_crate_layout_generated.rs' ':(exclude)src/v1/stage0/src/gunbc_stage0_crate_partition_generated.rs' ':(exclude)src/v1/stage0/src/lib.rs' ':(exclude)src/v1/stage0/src/std_algebra.rs' ':(exclude)src/v1/stage0/src/std_checked_arithmetic.rs' ':(exclude)src/v1/stage0/src/std_coercion.rs' ':(exclude)src/v1/stage0/src/std_computation.rs' ':(exclude)src/v1/stage0/src/std_constructors.rs' ':(exclude)src/v1/stage0/src/std_content_hash.rs' ':(exclude)src/v1/stage0/src/std_currency.rs' ':(exclude)src/v1/stage0/src/std_decl_ref.rs' ':(exclude)src/v1/stage0/src/std_disposition.rs' ':(exclude)src/v1/stage0/src/std_dissolution.rs' ':(exclude)src/v1/stage0/src/std_effects.rs' ':(exclude)src/v1/stage0/src/std_emit_model.rs' ':(exclude)src/v1/stage0/src/std_error_primitives.rs' ':(exclude)src/v1/stage0/src/std_execution_mode.rs' ':(exclude)src/v1/stage0/src/std_graph.rs' ':(exclude)src/v1/stage0/src/std_http_path.rs' ':(exclude)src/v1/stage0/src/std_induction.rs' ':(exclude)src/v1/stage0/src/std_integer.rs' ':(exclude)src/v1/stage0/src/std_interface_summary.rs' ':(exclude)src/v1/stage0/src/std_iteration.rs' ':(exclude)src/v1/stage0/src/std_keyed_roster.rs' ':(exclude)src/v1/stage0/src/std_keyed_row.rs' ':(exclude)src/v1/stage0/src/std_logic.rs' ':(exclude)src/v1/stage0/src/std_machine_constraints.rs' ':(exclude)src/v1/stage0/src/std_magnitude.rs' ':(exclude)src/v1/stage0/src/std_measure.rs' ':(exclude)src/v1/stage0/src/std_nat.rs' ':(exclude)src/v1/stage0/src/std_node.rs' ':(exclude)src/v1/stage0/src/std_occurrence_binding.rs' ':(exclude)src/v1/stage0/src/std_occurrence_binding_candidates.rs' ':(exclude)src/v1/stage0/src/std_occurrence_binding_resolve.rs' ':(exclude)src/v1/stage0/src/std_occurrence_identity.rs' ':(exclude)src/v1/stage0/src/std_pareto.rs' ':(exclude)src/v1/stage0/src/std_process_termination.rs' ':(exclude)src/v1/stage0/src/std_realization_schedule.rs' ':(exclude)src/v1/stage0/src/std_reference_binding_observation.rs' ':(exclude)src/v1/stage0/src/std_roster_frontier.rs' ':(exclude)src/v1/stage0/src/std_serialization.rs' ':(exclude)src/v1/stage0/src/std_source_annotation.rs' ':(exclude)src/v1/stage0/src/std_syntax.rs' ':(exclude)src/v1/stage0/src/std_termination.rs' ':(exclude)src/v1/stage0/src/std_trait_derive_shape.rs' ':(exclude)src/v1/stage0/src/std_types.rs' ':(exclude)src/v1/stage0/src/std_unicode_types.rs' ':(exclude)src/v1/stage0/src/std_witness_admission.rs' ':(exclude)src/v1/stage0/src/v1_compiler_annotation_bind.rs' ':(exclude)src/v1/stage0/src/v1_compiler_artifact.rs' ':(exclude)src/v1/stage0/src/v1_compiler_closure_stub_v2_std_integer_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_closure_stub_v2_std_text_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_coercion.rs' ':(exclude)src/v1/stage0/src/v1_compiler_compile.rs' ':(exclude)src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_complexity.rs' ':(exclude)src/v1/stage0/src/v1_compiler_dag_collect.rs' ':(exclude)src/v1/stage0/src/v1_compiler_dag_collect_support.rs' ':(exclude)src/v1/stage0/src/v1_compiler_effect_derivation.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_core_support.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_go.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_python.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_frontend_observation.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_access.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_cycle.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_emit_info.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_env.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_items.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_lookup.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_method.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_occurrence_binding.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_patterns.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_resolve.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_service.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_sigs.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_types.rs' ':(exclude)src/v1/stage0/src/v1_compiler_languages.rs' ':(exclude)src/v1/stage0/src/v1_compiler_normalize.rs' ':(exclude)src/v1/stage0/src/v1_compiler_ownership.rs' ':(exclude)src/v1/stage0/src/v1_compiler_parse.rs' ':(exclude)src/v1/stage0/src/v1_compiler_resolve.rs' ':(exclude)src/v1/stage0/src/v1_compiler_runtime_go.rs' ':(exclude)src/v1/stage0/src/v1_compiler_runtime_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_stage0_crates.rs' ':(exclude)src/v1/stage0/src/v1_compiler_tokenize.rs' ':(exclude)src/v1/stage0/src/v1_compiler_trace.rs' ':(exclude)src/v1/stage0/src/v1_compiler_trait_derive_emit.rs' ':(exclude)src/v1/stage0/src/v1_compiler_workspace_members.rs' ':(exclude)src/v1/stage0/src/v1_gunbc_namespace_reference_derived_closure_production_observations.rs' ':(exclude)src/v1/stage0/src/v1_gunbc_occurrence_binding_parser_walk.rs' ':(exclude)src/v1/stage0/src/v1_probe_emit_interp.rs' ':(exclude)src/v1/stage0/src/v1_rt.rs' ':(exclude)src/v1/stage0/src/v1_std_core.rs' ':(exclude)src/v1/stage0/src/v1_test_non_ascii_perf_fixture.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_caret_parse_smoke_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_dag_parse_continuation_operator_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_namespace_reference_derived_closure_production_admissions_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_occurrence_identity_debt_receipt_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_ordinary_frontend_observation_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_pattern_binder_declaration_node_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_required_expr_newline_continuation_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_binding_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_capture_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_erasure_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_round_trip_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_target_emission_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_complexity_eviction_hazard_test.rs' ':(exclude)src/v1/stage0/src/wt_a.rs' ':(exclude)src/v1/stage0/src/wt_b.rs' ':(exclude)src/v1/stage0/src/wt_common.rs')" + AUTHORED_CONFLICTS="$(git diff --name-only --diff-filter=U -- . ':(exclude).github/workflows/ci.yml' ':(exclude).github/workflows/falsifier.yml' ':(exclude).github/workflows/fleet-converge.yml' ':(exclude).gitignore' ':(exclude).gitattributes' ':(exclude)ROADMAP.md' ':(exclude)DESIGN.md' ':(exclude).githooks/pre-push' ':(exclude).githooks/pre-commit' ':(exclude).githooks/generated-artifact-merge' ':(exclude)dag/gunbc/stage0_crate_layout_generated.dag' ':(exclude)src/v1/stage0/src/bootstrap_stage0_crate_layout_generated.rs' ':(exclude)dag/gunbc/stage0_crate_partition_generated.dag' ':(exclude)dag/gunbc/stage0_emit_plan_generated.dag' ':(exclude)src/v1/stage0/src/v1_interpreter_dispatch_generated.rs' ':(exclude)docs/plans/idea-machine.md' ':(exclude)docs/plans/algebraic-rewrite-optimization.md' ':(exclude)docs/plans/dag-v2-defork-audit.md' ':(exclude)docs/plans/v2-self-hosting.md' ':(exclude)docs/plans/m5-fixture-store-consolidation.md' ':(exclude)docs/plans/model-realization-fork.md' ':(exclude)docs/plans/value-null-split.md' ':(exclude)docs/plans/ci-merge-freshness.md' ':(exclude)docs/plans/seed-debt-bundle-item-2.md' ':(exclude)docs/plans/axiom-syllogism-lens.md' ':(exclude)docs/plans/invert-hand-maintained.md' ':(exclude)docs/plans/fail-closed-lockdown.md' ':(exclude)docs/plans/format-model-reconciliation.md' ':(exclude)docs/plans/regime2-shared-emission-fold.md' ':(exclude)docs/plans/shell-emission-model.md' ':(exclude)docs/plans/construction-justification-rule.md' ':(exclude)docs/plans/emission-ingestion-inverse.md' ':(exclude)docs/plans/testgen-oracle.md' ':(exclude)docs/plans/disposition-carrier.md' ':(exclude)docs/plans/fold-ergonomics.md' ':(exclude)docs/plans/m4-universal-hermetic-corpus.md' ':(exclude)docs/plans/wiring-liveness-preflight.md' ':(exclude)docs/plans/interpreter-kernel-d.md' ':(exclude)docs/plans/inert-layer-lens.md' ':(exclude)docs/plans/compile-clean-forcecheck.md' ':(exclude)docs/plans/expressibility-frontier.md' ':(exclude)docs/plans/p1-where-clause-lowering.md' ':(exclude)docs/plans/intent-linearity-design-draft.md' ':(exclude)docs/plans/solve-higher-order-design.md' ':(exclude)docs/plans/language-target-self-host-frontier.md' ':(exclude)docs/plans/self-applying-lenses.md' ':(exclude)docs/plans/budget-tree.md' ':(exclude)docs/plans/cardinality-refinement.md' ':(exclude)docs/plans/compute-envelope-model.md' ':(exclude)docs/plans/input-envelope-roadmap.md' ':(exclude)docs/plans/ci-selection-vs-scheduling.md' ':(exclude)docs/plans/ci-process-end-to-end.md' ':(exclude)docs/plans/ci-oom-reclassification.md' ':(exclude)docs/plans/bounded-input-cost-envelope-scheduling.md' ':(exclude)docs/plans/ci-humming.md' ':(exclude)docs/plans/edge-b-rust-dag-provenance-brief.md' ':(exclude)docs/plans/resolver-type-name-collision-wall.md' ':(exclude)docs/plans/realization-measurement-loop.md' ':(exclude)docs/plans/host-effect-orchestration.md' ':(exclude)docs/plans/ci-floor-fractal-gantt.md' ':(exclude)docs/plans/resolver-pathology-profile-receipt.md' ':(exclude)docs/plans/representation-minimization.md' ':(exclude)docs/plans/reference-grounding-migration.md' ':(exclude)docs/plans/typescript-gap-census.md' ':(exclude)docs/plans/cli-run-hollowing-plan.md' ':(exclude)docs/plans/fleet-acceptance-criteria.md' ':(exclude)docs/plans/structural-quadratic-wall-coverage-audit.md' ':(exclude)docs/plans/ci-performance-tanking-evidence.md' ':(exclude)docs/plans/extdeps-modeling-preflight.md' ':(exclude)docs/plans/discrete-cost-derivation.md' ':(exclude)docs/plans/v2-complexity-capability-parity.md' ':(exclude)src/v1/stage0/src/compiler_tests.rs' ':(exclude)src/v1/stage0/src/extdeps_cargo.rs' ':(exclude)src/v1/stage0/src/extdeps_cargo_version.rs' ':(exclude)src/v1/stage0/src/extdeps_container_oci_digest.rs' ':(exclude)src/v1/stage0/src/extdeps_external_authority.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_dag_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_dag_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_dag_types.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_go_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_go_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_go_types.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_python_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_python_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_python_types.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_rust_emit.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_rust_syntax.rs' ':(exclude)src/v1/stage0/src/extdeps_languages_rust_types.rs' ':(exclude)src/v1/stage0/src/extdeps_units_dimensionless.rs' ':(exclude)src/v1/stage0/src/extdeps_units_iec_80000_13.rs' ':(exclude)src/v1/stage0/src/extdeps_units_iso8601.rs' ':(exclude)src/v1/stage0/src/extdeps_units_iso_80000_3.rs' ':(exclude)src/v1/stage0/src/extdeps_uri.rs' ':(exclude)src/v1/stage0/src/extdeps_uri_path.rs' ':(exclude)src/v1/stage0/src/extdeps_version.rs' ':(exclude)src/v1/stage0/src/extdeps_version_semver.rs' ':(exclude)src/v1/stage0/src/gunbc_namespace_reference_derived_closure_admission.rs' ':(exclude)src/v1/stage0/src/gunbc_namespace_reference_derived_closure_contract.rs' ':(exclude)src/v1/stage0/src/gunbc_rust_decl_type_overlay.rs' ':(exclude)src/v1/stage0/src/gunbc_stage0_crate_layout_generated.rs' ':(exclude)src/v1/stage0/src/gunbc_stage0_crate_partition_generated.rs' ':(exclude)src/v1/stage0/src/lib.rs' ':(exclude)src/v1/stage0/src/std_algebra.rs' ':(exclude)src/v1/stage0/src/std_checked_arithmetic.rs' ':(exclude)src/v1/stage0/src/std_coercion.rs' ':(exclude)src/v1/stage0/src/std_computation.rs' ':(exclude)src/v1/stage0/src/std_constructors.rs' ':(exclude)src/v1/stage0/src/std_content_hash.rs' ':(exclude)src/v1/stage0/src/std_currency.rs' ':(exclude)src/v1/stage0/src/std_decl_ref.rs' ':(exclude)src/v1/stage0/src/std_disposition.rs' ':(exclude)src/v1/stage0/src/std_dissolution.rs' ':(exclude)src/v1/stage0/src/std_effects.rs' ':(exclude)src/v1/stage0/src/std_emit_model.rs' ':(exclude)src/v1/stage0/src/std_error_primitives.rs' ':(exclude)src/v1/stage0/src/std_execution_mode.rs' ':(exclude)src/v1/stage0/src/std_graph.rs' ':(exclude)src/v1/stage0/src/std_http_path.rs' ':(exclude)src/v1/stage0/src/std_induction.rs' ':(exclude)src/v1/stage0/src/std_integer.rs' ':(exclude)src/v1/stage0/src/std_interface_summary.rs' ':(exclude)src/v1/stage0/src/std_iteration.rs' ':(exclude)src/v1/stage0/src/std_keyed_roster.rs' ':(exclude)src/v1/stage0/src/std_keyed_row.rs' ':(exclude)src/v1/stage0/src/std_logic.rs' ':(exclude)src/v1/stage0/src/std_machine_constraints.rs' ':(exclude)src/v1/stage0/src/std_magnitude.rs' ':(exclude)src/v1/stage0/src/std_measure.rs' ':(exclude)src/v1/stage0/src/std_nat.rs' ':(exclude)src/v1/stage0/src/std_node.rs' ':(exclude)src/v1/stage0/src/std_occurrence_binding.rs' ':(exclude)src/v1/stage0/src/std_occurrence_binding_candidates.rs' ':(exclude)src/v1/stage0/src/std_occurrence_binding_resolve.rs' ':(exclude)src/v1/stage0/src/std_occurrence_identity.rs' ':(exclude)src/v1/stage0/src/std_pareto.rs' ':(exclude)src/v1/stage0/src/std_process_termination.rs' ':(exclude)src/v1/stage0/src/std_realization_schedule.rs' ':(exclude)src/v1/stage0/src/std_reference_binding_observation.rs' ':(exclude)src/v1/stage0/src/std_reference_derived_closure.rs' ':(exclude)src/v1/stage0/src/std_roster_frontier.rs' ':(exclude)src/v1/stage0/src/std_serialization.rs' ':(exclude)src/v1/stage0/src/std_source_annotation.rs' ':(exclude)src/v1/stage0/src/std_syntax.rs' ':(exclude)src/v1/stage0/src/std_termination.rs' ':(exclude)src/v1/stage0/src/std_trait_derive_shape.rs' ':(exclude)src/v1/stage0/src/std_types.rs' ':(exclude)src/v1/stage0/src/std_unicode_types.rs' ':(exclude)src/v1/stage0/src/std_witness_admission.rs' ':(exclude)src/v1/stage0/src/v1_compiler_annotation_bind.rs' ':(exclude)src/v1/stage0/src/v1_compiler_artifact.rs' ':(exclude)src/v1/stage0/src/v1_compiler_closure_stub_v2_std_integer_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_closure_stub_v2_std_text_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_coercion.rs' ':(exclude)src/v1/stage0/src/v1_compiler_compile.rs' ':(exclude)src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_complexity.rs' ':(exclude)src/v1/stage0/src/v1_compiler_dag_collect.rs' ':(exclude)src/v1/stage0/src/v1_compiler_dag_collect_support.rs' ':(exclude)src/v1/stage0/src/v1_compiler_effect_derivation.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_core_support.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_go.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_python.rs' ':(exclude)src/v1/stage0/src/v1_compiler_emit_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_frontend_observation.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_access.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_cycle.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_emit_info.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_env.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_items.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_lookup.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_method.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_occurrence_binding.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_patterns.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_resolve.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_service.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_sigs.rs' ':(exclude)src/v1/stage0/src/v1_compiler_infer_types.rs' ':(exclude)src/v1/stage0/src/v1_compiler_languages.rs' ':(exclude)src/v1/stage0/src/v1_compiler_normalize.rs' ':(exclude)src/v1/stage0/src/v1_compiler_ownership.rs' ':(exclude)src/v1/stage0/src/v1_compiler_parse.rs' ':(exclude)src/v1/stage0/src/v1_compiler_resolve.rs' ':(exclude)src/v1/stage0/src/v1_compiler_runtime_go.rs' ':(exclude)src/v1/stage0/src/v1_compiler_runtime_rust.rs' ':(exclude)src/v1/stage0/src/v1_compiler_stage0_crates.rs' ':(exclude)src/v1/stage0/src/v1_compiler_tokenize.rs' ':(exclude)src/v1/stage0/src/v1_compiler_trace.rs' ':(exclude)src/v1/stage0/src/v1_compiler_trait_derive_emit.rs' ':(exclude)src/v1/stage0/src/v1_compiler_workspace_members.rs' ':(exclude)src/v1/stage0/src/v1_gunbc_namespace_reference_derived_closure_production_observations.rs' ':(exclude)src/v1/stage0/src/v1_gunbc_occurrence_binding_parser_walk.rs' ':(exclude)src/v1/stage0/src/v1_probe_emit_interp.rs' ':(exclude)src/v1/stage0/src/v1_rt.rs' ':(exclude)src/v1/stage0/src/v1_std_core.rs' ':(exclude)src/v1/stage0/src/v1_test_non_ascii_perf_fixture.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_caret_parse_smoke_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_dag_parse_continuation_operator_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_namespace_reference_derived_closure_production_admissions_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_occurrence_identity_debt_receipt_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_ordinary_frontend_observation_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_parser_declaration_category_matrix_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_parser_declaration_matrix_support.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_pattern_binder_declaration_node_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_reference_derived_closure_witness_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_required_expr_newline_continuation_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_binding_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_capture_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_erasure_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_round_trip_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_annotation_target_emission_test.rs' ':(exclude)src/v1/stage0/src/v1_tests_claim_v1_complexity_eviction_hazard_test.rs' ':(exclude)src/v1/stage0/src/wt_a.rs' ':(exclude)src/v1/stage0/src/wt_b.rs' ':(exclude)src/v1/stage0/src/wt_common.rs')" if [ -n "$AUTHORED_CONFLICTS" ]; then echo "heal refused: binary/source revision skew, and the remedy CONFLICTS in authored files" >&2 echo " conflicting paths:" >&2 diff --git a/ROADMAP.md b/ROADMAP.md index 905113f36d4..f301cc11842 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -10,11 +10,23 @@ Parked context (non-dispatchable): [compiler algorithm survey](docs/plans/compil The graph has sixteen lanes: SCM compatibility · namespace · P-derive · observation · placement · compute · CI cost · CI control · generated artefacts · v1 exit (four finish lines: compiler fixed point, interpreter deleted, products v1-free, zero hand-maintained Rust) · shell · roadmap runtime · fleet/hygiene · judgment · hermetic toolchain · compiler-guarantee (the DESIGN §4b ladder climbs; rung STATE lives in the guarantee claims carrier and is emitted, never restated in tickets — [gap analysis](docs/plans/compiler-guarantee-recovery-gap-analysis.md)). The three independent SCM R0 models, the separate P−1 evidence carrier, R1 compatibility-shape extraction, and P0 user-contract/landing spine are accepted; the operator-authored P1 proof-kernel node is active and fail-closed pending its first discriminating closing validation, while P2 and later product lanes remain parked. The toolchain pin model is an independent root. The P-derive receipt feeds the emitter fixed point, so the v1 chain nests under it. Compute owns the one contract everything else asks for work through — an exact subject in, a typed ending and the outputs it promised back out — and it sits ABOVE CI rather than inside it, because owning CI, converging the fleet, serving models and eventually judging changes are four consumers of one fabric, not four execution systems. It grounds on the signed realization spine rather than minting a second scheduler beside it. CI control owns who starts, queues and hands out required work and how its result reaches GitHub; CI cost owns how much computation that work performs. Fleet owns whether a merge to main becomes applied machine state and whether that question has one answer. Node fields define boundary, first slice, RED control, exclusions, owner, and handback. -**Focused view — the compute fabric + the infrastructure stabilisation lanes + fleet convergence from main.** 107 active deliverable(s) in other lanes are declared in the authority and hidden here; nothing is deleted or parked by focusing. Clear `roadmap_focus_selection` in `dag/gunbc/roadmap_authority.dag` to restore the full page. 6 of the rows below are NOT lane deliverables — they are prerequisites pulled in from other lanes because lane work is blocked on them: observation-scoped-run-consumer, observation-scoped-run-seed-growth-justification, observation-scoped-run-seed-growth-justification-closing-contract, shell-gate-migration, shell-effectplan-to-bash, shell-typed-invocation. Hidden lanes remain readable through their carriers: [docs/plans/dag-scm-design.md](docs/plans/dag-scm-design.md) · [docs/plans/namespace-unique-on-chain-operational-plan.md](docs/plans/namespace-unique-on-chain-operational-plan.md) · [docs/plans/v2-self-hosting.md](docs/plans/v2-self-hosting.md) · [src/v2/compiler/05_emit.dag](src/v2/compiler/05_emit.dag) · [docs/plans/progress-observation-design.md](docs/plans/progress-observation-design.md) · [src/v2/compiler/self_host/candidate_generation.dag](src/v2/compiler/self_host/candidate_generation.dag) · [src/v2/compiler/self_host/frontier.dag](src/v2/compiler/self_host/frontier.dag) · [dag/gunbc/v1_deletion_plan.dag](dag/gunbc/v1_deletion_plan.dag) · [dag/gunbc/stage0_rust_host_observation.dag](dag/gunbc/stage0_rust_host_observation.dag) · [docs/plans/witness-realization-plan.md](docs/plans/witness-realization-plan.md) · [src/v1/05_emit_rust.dag](src/v1/05_emit_rust.dag) · [dag/gunbc/v1_interpreter_primitive_surface.dag](dag/gunbc/v1_interpreter_primitive_surface.dag) · [docs/plans/shell-to-dag-residual-census-and-arc-completion.md](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) · [docs/plans/roadmap-workspace-ux-plan.md](docs/plans/roadmap-workspace-ux-plan.md) · [docs/plans/provider-control-interface-audit.md](docs/plans/provider-control-interface-audit.md) · [docs/plans/enforcement-intent-design.md](docs/plans/enforcement-intent-design.md) · [docs/plans/hermetic-tool-provisioning-design.md](docs/plans/hermetic-tool-provisioning-design.md) · [dag/gunbc/capability_binding.dag](dag/gunbc/capability_binding.dag) · [dag/gunbc/roadmap_component.dag](dag/gunbc/roadmap_component.dag) · [dag/gunbc/design_document.dag](dag/gunbc/design_document.dag) · [docs/plans/compiler-guarantee-recovery-gap-analysis.md](docs/plans/compiler-guarantee-recovery-gap-analysis.md) · [docs/plans/cardinality-refinement.md](docs/plans/cardinality-refinement.md). +**Focused view — the compute fabric + the infrastructure stabilisation lanes + fleet convergence from main + the namespace closure.** 95 active deliverable(s) in other lanes are declared in the authority and hidden here; nothing is deleted or parked by focusing. Clear `roadmap_focus_selection` in `dag/gunbc/roadmap_authority.dag` to restore the full page. 6 of the rows below are NOT lane deliverables — they are prerequisites pulled in from other lanes because lane work is blocked on them: observation-scoped-run-consumer, observation-scoped-run-seed-growth-justification, observation-scoped-run-seed-growth-justification-closing-contract, shell-gate-migration, shell-effectplan-to-bash, shell-typed-invocation. Hidden lanes remain readable through their carriers: [docs/plans/dag-scm-design.md](docs/plans/dag-scm-design.md) · [docs/plans/v2-self-hosting.md](docs/plans/v2-self-hosting.md) · [src/v2/compiler/05_emit.dag](src/v2/compiler/05_emit.dag) · [docs/plans/progress-observation-design.md](docs/plans/progress-observation-design.md) · [src/v2/compiler/self_host/candidate_generation.dag](src/v2/compiler/self_host/candidate_generation.dag) · [src/v2/compiler/self_host/frontier.dag](src/v2/compiler/self_host/frontier.dag) · [dag/gunbc/v1_deletion_plan.dag](dag/gunbc/v1_deletion_plan.dag) · [dag/gunbc/stage0_rust_host_observation.dag](dag/gunbc/stage0_rust_host_observation.dag) · [docs/plans/witness-realization-plan.md](docs/plans/witness-realization-plan.md) · [src/v1/05_emit_rust.dag](src/v1/05_emit_rust.dag) · [dag/gunbc/v1_interpreter_primitive_surface.dag](dag/gunbc/v1_interpreter_primitive_surface.dag) · [docs/plans/shell-to-dag-residual-census-and-arc-completion.md](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) · [docs/plans/roadmap-workspace-ux-plan.md](docs/plans/roadmap-workspace-ux-plan.md) · [docs/plans/provider-control-interface-audit.md](docs/plans/provider-control-interface-audit.md) · [docs/plans/enforcement-intent-design.md](docs/plans/enforcement-intent-design.md) · [docs/plans/hermetic-tool-provisioning-design.md](docs/plans/hermetic-tool-provisioning-design.md) · [dag/gunbc/capability_binding.dag](dag/gunbc/capability_binding.dag) · [dag/gunbc/roadmap_component.dag](dag/gunbc/roadmap_component.dag) · [dag/gunbc/design_document.dag](dag/gunbc/design_document.dag) · [docs/plans/compiler-guarantee-recovery-gap-analysis.md](docs/plans/compiler-guarantee-recovery-gap-analysis.md) · [docs/plans/cardinality-refinement.md](docs/plans/cardinality-refinement.md). - [ ] **Describe what a command should do, instead of writing shell text** — Callers say which operation and which arguments; nobody hands over an opaque string of shell as the instruction. Why: Shell syntax embedded in decision-making code cannot be checked before it runs, and cannot be reused anywhere else. [authority](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) - [ ] **Turn a described plan into shell through one translator** — One place turns a described plan into a shell script, preserving order, captured output, and failure behaviour. Why: Deletes the per-workflow shell writers and the scripts assembled by gluing strings together. [authority](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) - [ ] **Move the build and commit checks onto described plans** — The checks choose described operations and the translator produces the shell; the shell pasted into workflow files stops being a separate source of truth. Why: Lets us test what a check does before it runs, and use it outside of GitHub Actions. [authority](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) +- [ ] **Write the closing check for: Work out what a name can see from where it is written, inside one file** — One executable check states when this is done, transcribed from the node's own bar: Each of the four cases fails on its own when the rule is broken: a neighbour written earlier goes missing, a declaration from another branch leaks in, one written further down leaks in, or two same-spelled declarations merge into one. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Work out what a name can see from where it is written, inside one file** — Make an ordinary compiled path produce, for each name written in a file, the declarations actually in view from that position, and record which one answered. Four cases, each shown on its own: an ordinary neighbour written earlier in the same file is in view; a declaration inside a different branch of a decision is not; a declaration written further down is not; and two declarations spelled the same at genuinely different positions stay distinct rather than merging. Why: The parser walk that can answer this already exists and is switched on, but nothing in an ordinary compile asks it. Until it is asked on a real compile, every claim about what a name can see rests on hand-built examples, and the four rules above are asserted rather than observed. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Work out what each name can see, and let that be what a file depends on** — From where a name is written, work outward to every declaration actually in view — including ordinary neighbours in the same file — respecting where things were written relative to each other. Then record, for each name, exactly which declaration answered it, and derive what each file depends on from those answers. Why: What a file depends on is currently written out by hand as a separate statement, so it can disagree with what the file actually refers to. Hundreds of files were patched by hand to keep those two in step, which is work that should never have existed: what a file depends on is a consequence of what it refers to, not a second thing to maintain. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) — requires all: namespace-structural-observations, namespace-pool-independence + - [ ] **Settle the names that genuinely could mean two things** — Using the real answers from the step before — not a count taken by searching text — go through every name that genuinely has more than one candidate and either refuse it on purpose or say which one is meant, by renaming or by writing it out in full. Why: Counting these by searching for text gives a number that is wrong in both directions, so it cannot tell anyone how much work is left or whether it is finished. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Make name lookup itself produce that one answer, and delete the old way** — The places that WORK OUT what a name refers to — type names, value names, and calls — produce their answer from the mechanisms built in the earlier steps, and the old habit of searching everything currently loaded is deleted rather than left beside them. This is the changeover of name lookup itself. What the rest of the compiler then does with that answer is the next step, not this one. Why: Until the old lookup is deleted the new mechanism is only advisory: both run, they can disagree, and the compiler can still quietly mean something other than what was written. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Make the rest of the compiler use only that one answer** — Type checking, call lookup, and argument-count checks all read the single decided answer. When the answer is 'unresolved' or 'ambiguous', they stop rather than carrying on with a guess. Why: While parts of the compiler still do their own lookup, the one shared rule is advisory rather than authoritative. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Do the same for imports** — Every place the newer compiler resolves an import or a dotted name reads the single decided answer, before the old mechanisms are deleted. Why: If imports are left behind, they become a second source of truth about what a name means — exactly the problem this whole chain exists to remove. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Delete every other way of deciding what a name means** — Once all ordinary consumers use the one rule, remove the older mechanisms: name-string matching, walking up the parent chain, the global pool of bare names, the import retry, and the hand-written seed mirror that counts none/one/many for itself inside the bootstrap compiler. Owning that last one HERE, rather than in the node that defines the canonical decision, is deliberate — see namespace_kernel_authority_amendment_note. Why: Leaving them in place means one name can still get different answers from different code paths. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Delete the import lines, and the grammar that allows them** — Every import line goes, and import stops being something the language can express at all: writing one becomes a parse error. What a file depends on is worked out from the names it actually mentions. Why: While import still parses, a file can declare one set of dependencies while its references need another, and the two can disagree. That is the second source of truth this whole chain exists to remove, and it survives every earlier step. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) +- [ ] **Write the closing check for: Answer names that live in another file, and derive what each file depends on** — One executable check states when this is done, transcribed from the node's own bar: Two mentions of one target give one dependency but stay two separate records of which mention produced it; and no dependency appears that no name asked for. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Answer names that live in another file, and derive what each file depends on** — Work out, for a name whose answer lives in another file, which declaration answers it, then derive from those answers what the file depends on. Keep two separate results: for every mention, which declaration answered it; and for the file, the set of distinct files it depends on. Repeated mentions of one target collapse only in the second. Why: What a file depends on is written out by hand today, so it can disagree with what the file actually refers to. Hundreds of files were patched by hand to keep the two in step — work that should never have existed, because what a file depends on follows from what it refers to. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) + - [ ] **Loading an unrelated file must not change any answer** — Run the same source twice — once with an unrelated file loaded alongside it, once without — and compare the whole result: which declaration each name answered to, which mention produced which dependency, what the file depends on, and which names refused. Nothing may differ. Why: Names currently resolve partly by luck: a file's names find their targets when some unrelated file elsewhere happens to have pulled those targets in. That makes every green result conditional on what else was loaded, and it is why removing dependency statements had to be stopped across the whole repository. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - [ ] **Write the closing check for: Account for the five hand-written files that progress output still needs** — One executable check states when this is done, transcribed from the node's own bar: Any new hand-written code for this outside those five files, or without this plan, is refused. The plan: Move this reach-through into a described operation, then hollow out the corresponding part of the command-line tool so generated code does the work; the hand-written bridge is deleted at that point. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](dag/gunbc/plans/cli_run_hollowing_plan.dag) - [ ] **Account for the five hand-written files that progress output still needs** — Showing progress while a command runs still relies on hand-written code, in exactly these five files and no others: dag/gunbc/observation_seed_render.dag; dag/test/claim/observation_seed_scoped_run_witness_test.dag; src/v1/stage0/src/cli_run.rs; src/v1/stage0/src/v1_interpreter.rs; src/v1/stage0/tests/scoped_run_observation.rs Why: Why it is needed: Showing progress while a command runs has to reach the point where the old runner starts a program. It needs to know which piece of code was chosen, hold back error output until it can be shown in the right order, and report exactly one ending — and the rules for all of that live in the substrate, not in Rust. [authority](dag/gunbc/plans/cli_run_hollowing_plan.dag) - [ ] **Show honest progress while a command runs** — Running a command announces that it started and reports exactly one ending. In a terminal it updates in place; piped to a file it appends plain lines. Why: Progress is printed raw today, so a terminal shows a mess and a log file shows something different again. [authority](docs/plans/progress-observation-design.md) diff --git a/dag/gunbc/ci_layer_roots.dag b/dag/gunbc/ci_layer_roots.dag index 663f92aea52..a41475a13e0 100644 --- a/dag/gunbc/ci_layer_roots.dag +++ b/dag/gunbc/ci_layer_roots.dag @@ -216,6 +216,12 @@ data witness_exclusion_frontier: List = [ reason: "transport-run witness consumed by v2.workflow.module_binding_supply_transport (same posture as crate_layout; see crate_layout_transport_exclusion_note)", dissolution: unbound_dissolution(description: "the crate-layout artifact moves to host-driven generation with the index supplied at the boundary; rows delete together (crate_layout_transport_exclusion_note)") }, + WitnessExclusionRow { + pattern: "module_storage_row_supply_test.dag", + classification: FixtureExplicitRoster, + reason: "transport-run witness consumed by v2.workflow.module_binding_supply_transport: it projects off the derived ModuleStorageIndex, and the live index exists only inside that transport's overlay, so a hermetic discovery run reads host_module_bindings = Empty and REFUSES -- the vacuity guard working, correct behaviour in the wrong lane. FixtureExplicitRoster and NOT OfflineLocalRecipe, which is the classification the crate_layout siblings carry and which I copied first: the path-policy fold maps an offline row to LegacyFrozenPathDeferral only when gunbc.witness_deferral_freeze already names it, and otherwise to UnclassifiedPathDeferral, which is refused (5 refusals, run 31567336073). Those siblings predate the 2026-08-04 ruling and are held by the frozen baseline, which may only SHRINK, so a NEW witness cannot join them and must not try. FixtureExplicitRoster is the honest arm and reaches WitnessHasExecutingConsumer: the executing consumer is the declared roster module_binding_supply_claims, which is what the transport runs -- one authority, no second list of function names. Do NOT give it bin_witness_wet_entries rows", + dissolution: unbound_dissolution(description: "the module-binding index is supplied at the boundary rather than through an overlay source root; this row deletes with the crate_layout family (crate_layout_transport_exclusion_note)") + }, WitnessExclusionRow { pattern: "self_host/compiler_closure_emit_from_ingest_test.dag", classification: OfflineLocalRecipe, @@ -969,9 +975,24 @@ data v1_claim_scoped_witness_entries: List = [ function: "", kind: CorpusWitnessKind, }, + ScheduleWitnessEntry { + entry: "src/v1/tests/claim/parser_declaration_category_matrix_witness_test.dag", + function: "", + kind: CorpusWitnessKind, + }, + ScheduleWitnessEntry { + entry: "src/v1/tests/claim/import_deletion_bridge_witness_test.dag", + function: "", + kind: CorpusWitnessKind, + }, + ScheduleWitnessEntry { + entry: "src/v1/tests/claim/reference_derived_closure_witness_test.dag", + function: "", + kind: CorpusWitnessKind, + }, ] -data v1_claim_scoped_witness_batch_note: String = "This is one INSTANCE of std.realization_schedule ScopedWitnessBatch, not a parser branch in the floor mechanism. The four file-grain rows expand through ScheduleWitnessEntry's empty-function authority, so adding or renaming a test fn changes no roster. Their dag plus src/v1 source-root envelope admits the v1 compiler closure without widening witness_layer_roots for unrelated witnesses. SelectionApplied is deliberate: normal affected-set selection avoids paying the measured closure materialization on unrelated PRs; every unselected roster row still emits an exact-head ScopedWitnessSelectionSkipped receipt with provenance, while a provenance failure refuses loudly and never widens. The executor's own module universe is a distinct fact carried by ScopedWitnessBatch.execution_authority as InheritedWalkSourceRoots: the worker resolves affected_set_floor_runner, witness_entry_eligibility_census, and post-discovery cost/attribution projectors from the enclosing walk's roots while the witness subject index remains exactly dag plus src/v1. Adding src/v2 to this batch's witness roots was considered and refused because it widens the subject envelope, moves the receipt digest, and invalidates the measured clamp basis; executor-authority dependencies never enter the witness roots as an absorbing fallback. The profile declares a substantial whole-tree resolve, Hermetic effects, a batch-owned 720-second clamp, and SequentialChildProcess isolation. RECEIPTED RAISE (2026-08-14, PR #8259 — clamp derivation, three questions): (Q1 WHY SLOWER?) Scoped roster unchanged — still five file-grain rows expanding to ~30 witnesses. Continuation-operator witnesses and source-audit rows run in ordinary discovery, not this batch. Run 31775746910 @ 81c90babc journal: ~390s closure materialization before first witness line, ~30s witness eval, ~152s post-batch receipts; every expanded witness completed before walk-terminal failed. Growth is closure-materialization wall, not scoped enrollment. Correlates with editing src/v1/02_parse.dag (parser is in the dag+src/v1 scoped closure) but magnitude is not decomposed here — tracked by module-grain-materialization lane; this raise does not discharge that open cost. (Q2 WHY 720?) Policy chain from gunbc.ci_spec gunbc_ci_floor_batch_clamp_note: measured scoped-worker total wall × 1.2 fleet margin, rounded up to whole-minute grain — the same derivation family #7924 used for 360→480. This batch carries per_unit_ms=0, so the clamp bounds fixed-roster closure materialization, not witness-count creep (discovery batch uses overhead+rate for that). 565s measured wall × 1.2 = 678s → 720s. (Q3 WAS 480 DERIVED?) Yes — #7924 run 31223532865 @ 93b1373751 FLOOR-BATCH-OVER-BUDGET wall_ms=375553 clamp_ms=360000, locally reproduced; 375.553s × 1.2 = 451s → 480s whole-minute grain under the same policy. THE ENROLLMENT THAT GREW THE BATCH: none. Failure was over_budget mislabeled WitnessRed because failure_details stayed empty. RECEIPTED RAISE (2026-08-08, PR #7924 CI run 31223532865 @ 93b1373751): scoped child FLOOR-BATCH-OVER-BUDGET wall_ms=375553 clamp_ms=360000 units=27 — reproduced locally on the same commit after merge with main. Prior 360s basis (199.393s arm64 x 1.8 = 358.9s) is stale: post-merge scoped-worker stage wall is ~376s on x86_local with the full executor-authority projector path. Sizing follows gunbc.ci_spec gunbc_ci_floor_batch_clamp_note fleet-margin policy (measured worst wall x 1.2, not host roulette): 375.553s x 1.2 = 451s, rounded up to 480s whole-minute grain (+33% over the retired 360s row). THE ENROLLMENT THAT GREW THE BATCH: none — wall is closure-materialization dominated, not a roster edit. The earlier 180-second basis measured only 96 seconds before the cost projector and renderers were reachable from the narrow scoped envelope, so it no longer described the executing batch. COST DISPOSITION: the approximately 103-second increase correlated with making the cost projector, histogram, and slowest-attribution renderer reachable while witness row evaluations themselves remain negligible per-row; that reporting/closure cost has not been decomposed in this scoped-scheduling change and is tracked by the real ROADMAP module-grain-materialization lane. Its trigger is a per-projector resolve/materialization attribution showing whether the three projections recompute identical semantic facts, followed by cached-vs-cold receipts before any optimization claim. The larger clamp must not normalize that open cost question into proportionality. per_unit_ms remains zero because row evaluation is negligible; this catches closure growth and deliberately does not price row-count growth until a file brings a distinct closure. Exact-head run 30713632253 proves SharedWalkProcess cannot satisfy this instance: ordinary discovery completed at 7.113GB RSS, then the floor vanished while materializing this second root closure before a batch-start line. The kernel cause is unclassified, but additive same-process residency is established. SequentialChildProcess makes the ordinary worker exit before this worker starts, releasing the first live set without claiming the stronger fresh-cgroup semantics of FreshJobProcess. v1_claim_scoped_witness_dissolve_on fires when the v2 parser owns these claims." +data v1_claim_scoped_witness_batch_note: String = "This is one INSTANCE of std.realization_schedule ScopedWitnessBatch, not a parser branch in the floor mechanism. The file-grain rows expand through ScheduleWitnessEntry's empty-function authority, so adding or renaming a test fn changes no roster. (This sentence used to pin the count at four while five rows were present, and is deliberately unpinned rather than re-pinned: a hand-maintained count beside a list the list itself decides is a second representation that rots silently — DESIGN section 3. Merge receipt 2026-08-14: main re-pinned the word four in this sentence while the SAME revision's raise receipt below counts five file-grain rows, so the two halves of one string disagreed about the population they describe. That is the rot this parenthetical predicted, observed inside one authority, which is why the unpinned reading is kept here rather than main's.) The roster is UNIFORMLY FILE-GRAIN and must stay so: witness_v1_claim_scoped_batch_is_file_grain_and_batch_owned asserts every entry carries an empty function, and the empty-function authority is what keeps a test-fn rename from being a roster edit. A function-grain enrollment was attempted here on 2026-08-10 to let one file mix green rows with deliberately-red ones; that witness caught it, correctly. The consequence for this lane is that a file is uniformly green or uniformly red, never mixed. The eight derived parser requirements that were authored as a deliberately-red sibling file greened when commit C landed role-directed minting, so they MOVED into parser_declaration_category_matrix_witness_test.dag as permanent regression controls and that file was deleted along with its eight expect-red admissions -- the requirement did not stop being a requirement when it started holding (DESIGN section 4b(4)). The bridge keystone (import_deletion_bridge_witness_test.dag) DID green when commit C landed, and per its own dissolution it moved here file-grain beside its two controls, its expect-red admission deleted. Established in BOTH directions rather than observed once: against main's parser the keystone FAILS and against commit C's it PASSES, while the provider-withheld control stays green in both, so the pass is the new type-declaration population and not a closure that happens to carry the name. What greened is the SUPPLIED-PROVIDER claim only -- the provider path is an input to the operation under test, so provider discovery and provider-file edge projection remain open and this row must not be read as closing them. Removing that admission also emptied known_red_probe_v1_envelope_roster, which un-enrolls gunbc_v1_claim_scoped_known_red_probe_batch and restores the positional clamp coverage its enrollment had broken; no clamp row was added, so no budget was raised and no signature is owed. Their dag plus src/v1 source-root envelope admits the v1 compiler closure without widening witness_layer_roots for unrelated witnesses. SelectionApplied is deliberate: normal affected-set selection avoids paying the measured closure materialization on unrelated PRs; every unselected roster row still emits an exact-head ScopedWitnessSelectionSkipped receipt with provenance, while a provenance failure refuses loudly and never widens. The executor's own module universe is a distinct fact carried by ScopedWitnessBatch.execution_authority as InheritedWalkSourceRoots: the worker resolves affected_set_floor_runner, witness_entry_eligibility_census, and post-discovery cost/attribution projectors from the enclosing walk's roots while the witness subject index remains exactly dag plus src/v1. Adding src/v2 to this batch's witness roots was considered and refused because it widens the subject envelope, moves the receipt digest, and invalidates the measured clamp basis; executor-authority dependencies never enter the witness roots as an absorbing fallback. The profile declares a substantial whole-tree resolve, Hermetic effects, a batch-owned 720-second clamp, and SequentialChildProcess isolation. RECEIPTED RAISE (2026-08-14, PR #8259 — clamp derivation, three questions): (Q1 WHY SLOWER?) Scoped roster unchanged — still five file-grain rows expanding to ~30 witnesses. Continuation-operator witnesses and source-audit rows run in ordinary discovery, not this batch. Run 31775746910 @ 81c90babc journal: ~390s closure materialization before first witness line, ~30s witness eval, ~152s post-batch receipts; every expanded witness completed before walk-terminal failed. Growth is closure-materialization wall, not scoped enrollment. Correlates with editing src/v1/02_parse.dag (parser is in the dag+src/v1 scoped closure) but magnitude is not decomposed here — tracked by module-grain-materialization lane; this raise does not discharge that open cost. (Q2 WHY 720?) Policy chain from gunbc.ci_spec gunbc_ci_floor_batch_clamp_note: measured scoped-worker total wall × 1.2 fleet margin, rounded up to whole-minute grain — the same derivation family #7924 used for 360→480. This batch carries per_unit_ms=0, so the clamp bounds fixed-roster closure materialization, not witness-count creep (discovery batch uses overhead+rate for that). 565s measured wall × 1.2 = 678s → 720s. (Q3 WAS 480 DERIVED?) Yes — #7924 run 31223532865 @ 93b1373751 FLOOR-BATCH-OVER-BUDGET wall_ms=375553 clamp_ms=360000, locally reproduced; 375.553s × 1.2 = 451s → 480s whole-minute grain under the same policy. THE ENROLLMENT THAT GREW THE BATCH: none. Failure was over_budget mislabeled WitnessRed because failure_details stayed empty. RECEIPTED RAISE (2026-08-08, PR #7924 CI run 31223532865 @ 93b1373751): scoped child FLOOR-BATCH-OVER-BUDGET wall_ms=375553 clamp_ms=360000 units=27 — reproduced locally on the same commit after merge with main. Prior 360s basis (199.393s arm64 x 1.8 = 358.9s) is stale: post-merge scoped-worker stage wall is ~376s on x86_local with the full executor-authority projector path. Sizing follows gunbc.ci_spec gunbc_ci_floor_batch_clamp_note fleet-margin policy (measured worst wall x 1.2, not host roulette): 375.553s x 1.2 = 451s, rounded up to 480s whole-minute grain (+33% over the retired 360s row). THE ENROLLMENT THAT GREW THE BATCH: none — wall is closure-materialization dominated, not a roster edit. The earlier 180-second basis measured only 96 seconds before the cost projector and renderers were reachable from the narrow scoped envelope, so it no longer described the executing batch. COST DISPOSITION: the approximately 103-second increase correlated with making the cost projector, histogram, and slowest-attribution renderer reachable while witness row evaluations themselves remain negligible per-row; that reporting/closure cost has not been decomposed in this scoped-scheduling change and is tracked by the real ROADMAP module-grain-materialization lane. Its trigger is a per-projector resolve/materialization attribution showing whether the three projections recompute identical semantic facts, followed by cached-vs-cold receipts before any optimization claim. The larger clamp must not normalize that open cost question into proportionality. per_unit_ms remains zero because row evaluation is negligible; this catches closure growth and deliberately does not price row-count growth until a file brings a distinct closure. Exact-head run 30713632253 proves SharedWalkProcess cannot satisfy this instance: ordinary discovery completed at 7.113GB RSS, then the floor vanished while materializing this second root closure before a batch-start line. The kernel cause is unclassified, but additive same-process residency is established. SequentialChildProcess makes the ordinary worker exit before this worker starts, releasing the first live set without claiming the stronger fresh-cgroup semantics of FreshJobProcess. v1_claim_scoped_witness_dissolve_on fires when the v2 parser owns these claims." data v1_claim_scoped_witness_batch: ScopedWitnessBatch = scoped_witness_batch( batch_id: "v1-claim-scoped-witnesses" as ScopedWitnessBatchId, diff --git a/dag/gunbc/ci_spec.dag b/dag/gunbc/ci_spec.dag index 768b4cdad12..f3d3c36b50f 100644 --- a/dag/gunbc/ci_spec.dag +++ b/dag/gunbc/ci_spec.dag @@ -258,8 +258,11 @@ data gunbc_ci_floor_batch_clamp_params: List = [ gunbc_ci_floor_positional_clamp(overhead_seconds: 540, per_unit_ms: 0), gunbc_ci_floor_positional_clamp(overhead_seconds: 120, per_unit_ms: 0), gunbc_ci_floor_positional_clamp(overhead_seconds: 125, per_unit_ms: 0), + gunbc_ci_floor_positional_clamp(overhead_seconds: 240, per_unit_ms: 1000), ] +data gunbc_ci_v1_envelope_known_red_probe_clamp_basis_note: String = "V1-ENVELOPE KNOWN-RED PROBE ROW (the last positional row, added 2026-08-14 on PR #8115): enrolling a row in gunbc.explicit_witness_admission known_red_probe_v1_envelope_roster makes v1_claim_scoped_known_red_probe_batch_enrolled true, which appends one RunnableDiscoveryBatch to the schedule. It is a discovery batch, not a RunnableScopedWitnessBatch, so it carries no scoped clamp of its own and falls to the positional table — the ENROLLMENT COUPLING clause in v2.workflow.ci_floor_plan v1_claim_scoped_known_red_probe_batch_note names exactly this obligation, and it is a fail-closed refusal at arm time rather than a mis-clamp. THE ENROLLMENT THAT GREW THE SCHEDULE, named because this family's template asks for it: src/v1/tests/claim/occurrence_binding_parser_walk_witness_test.dag :: parser_sibling_match_arm_pattern_binder_holds, admitted QuarantineProbeExpectRed because a match-arm pattern binder is stamped ParsedOccurrenceDeclaration with the arm's own ancestors, making the binder a SIBLING of the arm body — structurally red from authored source, not a defect this PR introduced. BASIS, and its honesty class: 95.6s measured wall for the enrolled single row (claim_batch --source-root dag --source-root src/v1 on that entry and function, local x86, cold process). That is NOT a fleet observation — the family's constants are denominated on the arm64 srv host class, and this host is not it — so the 240s overhead is deliberately ~2.5x the local measurement rather than the family's usual 1.6x spread factor, funding the unmeasured host-class difference explicitly instead of pretending the local number is a fleet number. It sits well under v1_claim_scoped_witness_batch's 480s, which shares the dag plus src/v1 envelope over a larger roster, so the ordering between the two rows stays sensible. WHAT THE MEASUREMENT ACTUALLY SPENT: 25.4s of the 95.6 is load_bare_edge_index on one pool entry — the whole-tree bare-reference edge index, a corpus-denominated fixed cost this batch pays like every other resolve, owned by the assembly-decomposition lane and not by this row. per_unit_ms is the signed family witness coefficient 1000, not 0, because the roster is a list that grows: a fixed overhead would silently absorb the second and third enrolled probe. DISSOLVE-ON: the roster empties (the note it enrolls under retires when closure-independent binding makes the dag plus src/v1 envelope unnecessary), at which point the batch unenrolls and this row is deleted with it." + data gunbc_ci_compile_clean_clamp_note: String = "COMPILE-CLEAN LEG CLAMP (prelude coverage, first slice): discharges the compile-clean portion of gunbc_ci_floor_batch_clamp_note FOLLOW-UP ROW (a) — the eager compile-clean install ran OUTSIDE every batch budget and could only red at the step cap, which is how a 15m32s green gate grew past 49 minutes across #7398/#7438 with nothing naming the growth (typecheck-perf investigation, PR #7490). Same shape as the batch clamps: clamp_ms = overhead_seconds*1000 + closure_units * per_unit_ms, where closure_units is the compiled closure's MODULE COUNT — a runtime fact of the scope disposition (whole-tree ~2725 today, affected-set proportional to the diff), so the clamp re-denominates with scope exactly as batch clamps re-denominate with the affected set. Over-clamp is FLOOR-COMPILE-CLEAN-OVER-BUDGET: a typed, located walk refusal (admission grain — the compile receipt's ok is untouched, per the signed admission/verdict split in gunbc_ci_floor_batch_wall_budget_note); never a rerun, never a widen. RED-control hook GUNBC_FLOOR_COMPILE_CLEAN_BUDGET_TIGHTEN_MS lowers the computed clamp (min), never raises. GROWTH DETECTION at row grain is NOT this clamp's job (the family margin ruling, 2026-07-25): per-row 2x drift against dated basis rows rides gunbc.witness_row_cost's comparator over dag/gunbc/compile_clean_cost_basis.tsv — the pass + module_typecheck rows of target/floor-compile-clean-cost-receipt.tsv, basis rows citing arm64 fleet run ids only, BasisAbsent counted until seeded from a cited run's own receipt FILE (the #7475 seeding pattern; an empty basis must count loudly, never read as no-drift). SEEDING SOURCE CORRECTED: this clause used to name the [compile-clean-cost] log lines, because the receipt file had no way off the runner and every row was mirrored into the step log to compensate — ~1,690 lines per floor run, and the same shape cost 8,614 more on the disclosure receipt. The ci job now uploads the receipts as the floor-receipts artifact (gunbc.ci_workflow ci_floor_receipts_upload_step), so seeding downloads the TSV instead of scraping a log, and the writers no longer mirror their bodies. The log lines are GONE, not merely deprecated: a seeding attempt that greps a run log for them will find nothing, which is why this clause is corrected rather than left to rot. BASIS OF THE CONSTANTS (worker-sized, 2026-07-31, from run 30602520347 @ 0ecb898, host srv1-01 arm64, post-#7490 fixes): whole-tree pass 258s over the ~1648-unit whole-tree entry closure (units = the leg's compiled closure module count, receipted by the first [compile-clean-cost] execution — NOT the ~2725-file module index, which overcounts by pool files outside every entry closure) = ~157ms/unit observed; per_unit_ms 320 is ~2x that rate, funding a full 2x fleet-spread host on top of the observed wall (the family margin ruling: a breach means content grew, never which host answered; pre-fix completing runs spread ~1.8x across hosts) — the batch family's 1000ms coefficient carries 1.4-1.7x the same way; overhead 60s covers the closure-size-independent tail (census fill parse ~13s fleet + governor arm). Whole-tree clamp = 60s + 1648*320ms = ~587s (~2.3x the observed 258s fleet wall). MECHANISM RECEIPTS (local x86, logic-not-cost host): green e2e via gunbc_ci_plan_artifact_plan — pass 310127ms/1648 units, WithinBudget, 1204 module rows, drift basis_absent=1205 counted, exit 0 (run at the pre-resize 200ms sizing; the arm proof is arithmetic-independent and the formula is witness-pinned at the final constants); RED control via GUNBC_FLOOR_COMPILE_CLEAN_BUDGET_TIGHTEN_MS=1 receipted beside it. DISPATCH: operator direction this session ('can we add the budget gating ... i suggest we share that model' — briansrls, 2026-07-31). OPERATOR SIGNATURE (briansrls, 2026-07-31): the worker-sized constants are affirmed as proposed — overhead_seconds 60, per_unit_ms 320 ('the clamp constants you chose are fine, please proceed') — so the refusal arm is signed-live per the family raise discipline; TIGHTENING may still land by ordinary receipt note, and the first fleet observations should tighten per_unit_ms toward the measured rate." data gunbc_ci_compile_clean_clamp: RunnableBatchClamp = RunnableBatchClamp { diff --git a/dag/gunbc/explicit_witness_admission.dag b/dag/gunbc/explicit_witness_admission.dag index 3018696a089..b3e3f39fa19 100644 --- a/dag/gunbc/explicit_witness_admission.dag +++ b/dag/gunbc/explicit_witness_admission.dag @@ -172,6 +172,14 @@ data explicit_witness_admissions: List = [ reason: "the english-language emit-ingest round trip returns false undiagnosed. Owner: the S2 emit family / behavioral-closure lane.", dissolution: unbound_dissolution(description: "the S2 behavioral-closure lane greens it locally, then this row deletes") ), + known_red_probe( + entry: "src/v1/tests/claim/occurrence_binding_parser_walk_witness_test.dag", + f: "parser_sibling_match_arm_pattern_binder_holds", + kind: CorpusWitnessKind, + budget: FastLaneEvalBudget, + reason: "STRUCTURALLY RED FROM AUTHORED SOURCE, and the parser is the reason rather than the witness. v1.compiler.parse stamps a match-arm pattern binder with the ARM's ancestors, so the binder is a SIBLING of the arm body rather than an ancestor of it; the binder's containment-derived LexicalExposure therefore has an exposing scope that does not contain the arm-body reference, and the isolation this witness asserts -- each arm's reference binding its own binder and refusing the other arm's -- cannot hold from authored source however the exposure authority is written. The file's own parser_sibling_match_arm_pattern_binder_holds_note has said so since the B1 rollup; what it lacked was an admission row, so the function stayed an ordinary green-required test that happened to be selected only when something in its import closure changed. It was newly selected by gunbc#8115's dag/std/occurrence_binding_resolve change and read as that PR's regression; it is not, and two bisect cycles on the wrong subject were the cost of the missing row. The equivalent property IS discharged, on a hand-built transport where the arm body nests under the binder occurrence, by occurrence_binding_candidates_witness -- so this row loses no coverage, it stops a known-structural red from being rediscovered as a new one.", + dissolution: unbound_dissolution(description: "the parser nests arm-body references under pattern-binder occurrences, or an equivalent exposure derivation lands that does not require that nesting; then this witness greens from authored source, this row deletes, and the function re-enrolls in namespace_reference_derived_closure_parser_integration_holds as its own note directs") + ), known_red_probe( entry: "dag/test/claim/legacy_test_behavior_disposition_acceptance_test.dag", f: "legacy_test_behavior_unclassified_frontier_is_zero", diff --git a/dag/gunbc/roadmap_authority.dag b/dag/gunbc/roadmap_authority.dag index ebe4d99071c..888b9e946a3 100644 --- a/dag/gunbc/roadmap_authority.dag +++ b/dag/gunbc/roadmap_authority.dag @@ -2819,11 +2819,13 @@ fn preamble() -> List { ] } +data namespace_lane_measured_frontier_note: String = "MEASURED FRONTIER OF THE NAMESPACE LANE, 2026-08-14. Superseding the 2026-08-13 revision, which was itself written to supersede a stale one and went stale again within a day as the lane moved -- so read this row as a dated measurement, not as a standing description.\n\nLANDED, by execution over ordinary source files: a total candidate universe parsed from the given source roots, refusing on unreadable, unparsable, duplicate or contradictory candidates rather than shrinking silently; fixed-point provider discovery with no provider path supplied by any fixture; transitive A to B to C in three iterations over three files yielding two edges; entry-invariant module exposure, asserted in both directions on identical bytes; qualified TYPE-position member projection, where the leaf selects the bucket and the qualifier selects the declaring module and two modules sharing a segment refuse as Ambiguous naming both; module-value declarations, since `data name: T = v` previously bound NO name and the derived closure was short by every module-value edge in the corpus; expression-position MODULE-HEAD binding; and module-scope constructor exposure, where a nested constructor and a nested field sit at identical containment depth and derive opposite answers.\n\nTHE IMMEDIATE FRONTIER, CORRECTED 2026-08-14 BECAUSE THE PRIOR REVISION OF THIS PARAGRAPH WENT STALE INSIDE ONE DAY. It described the head-only widen as live: p binds and admits its module file while the member never resolves, so an ABSENT OR AMBIGUOUS member still discovers a provider file. That is no longer true at one segment. A projection whose base identifies a module now resolves the segment among that module's own declarations and attributes the dependency to the TERMINAL member declaration; a missing member is a typed Unbound located at the segment, and an ambiguous one refuses naming both candidates. Reading a superseded frontier as current is not a harmless lag -- it directs the next reader to repair a fail-open that is already closed, which is why this correction is recorded rather than the paragraph rewritten in place.\n\nWHAT IS ACTUALLY OPEN, and it is a SHAPE obligation rather than a safety one. The one-segment repair is a correct wall and a wrong terminal implementation, because it holds two temporary representations beside each other: a projection segment still impersonates a FieldOccurrence, so the generic binding-compatibility relation was widened to admit module values, callables, types and constructors at a field reference -- held safe by which candidates this producer supplies, which is caller behavior rather than construction; and the module head is decided TWICE, once by the ordinary reference fold and again by a segment-index reconstruction. Both dissolve into one ReferencePath carrier fed by BOTH syntactic positions -- type p.TypeName and expression p.first -- which binds the head ONCE, consumes that accepted declaration identity rather than re-deriving it, descends one exact containment child per segment at arbitrary depth, and produces no dependency-ready outcome until the terminal declaration binds. Established by execution while designing it: a module member's containment parent IS the module declaration occurrence, so one containment-child step is the uniform segment rule and the qualifier suffix-match is REMOVABLE rather than something to generalise.\n\nA PREREQUISITE THIS ROW PREVIOUSLY DID NOT NAME, and an admission that must not be mistaken for a resolution. parser_sibling_match_arm_pattern_binder_holds is admitted as a known-red probe because the parser stamps a match-arm pattern binder and the arm body as SIBLINGS, so the binder's lexical exposure does not contain the body reference and the structural resolver cannot establish binder isolation from authored source. Admitting it keeps CI honest about a red that is structural from source; it does NOT make it a permanent state. When the structural resolver becomes the ORDINARY resolver, local lexical references must still bind, so the sequence is: repair parser containment for match arms, green the authored-source witness, DELETE its exact-red admission (and the conditional floor batch and clamp row that exist only to carry it), and retain the witness as a permanent regression control. Accumulating further CI accounting around this parser defect instead of repairing it is the wrong direction.\n\nSTILL OPEN BEYOND THAT: the authored corpus is written in the import-era bare style, so closure over dag + src/v2 refuses on bare String; the migration is DERIVED from old-versus-new declaration-identity equality, never hand-judged, because the population moves while the lane works (18,628 import lines across 2,884 files at this measurement, and one in-flight PR moves it by +147). The candidate universe is rebuilt every iteration and must be sealed ONCE before the differential, or the differential is unusable as a migration oracle. No ordinary compile path consumes any of this: dependency_resolution_facts_live still produces the import-era edges.\n\nTHE TERMINAL OBLIGATION THIS ROW ONCE OMITTED. rewire_type_env_import_str_binding_identity survives on main as a post-typecheck assembly operation. gunbc#8202 established that moving it earlier into build_type_env CHANGES typechecking results, so there is no narrow delete-the-pass repair, and gunbc#8227 optimized it while explicitly restating that optimization is not progress toward deleting it. Its removal belongs to the type-env single-authority cutover and must land BEFORE grammar deletion.\n\nNO PRODUCTION NODE IS ACCEPTED BY THIS, and updating a measured frontier is not accepting one. Imports, their grammar and their AST are all still present." + data roadmap_focus_selection_note: String = "THE ONE ROW THAT SETS THE VIEW. Focusing is a projection policy, not an edit — every node and edge stays declared in this file, the dispatch surface still sees the whole graph, and setting this back to NoFocus restores the full page with no recovery step. The projection states how many it is hiding, derived rather than transcribed, so a focused page can never read as a shorter roadmap.\n\nMOVED TO THE INFRASTRUCTURE LANES (operator direction, 2026-08-07). It previously selected the v1 exit lane and the compiler-guarantee climbs. Those remain real work and are unchanged in this file; they are off the page because forward movement on them was stopped. The stated reason is that infrastructure buckled underneath them — a fleet whose applied state cannot be answered without logging into machines, a required path whose cost is dominated by building and testing, machine faults arriving as reds that look like code faults, and generated artefacts conflicting over lists nobody disagrees about. Work carried out on top of that produces results nobody can trust, so the page shows the lanes that repair it and the lane that makes a merge to main become applied fleet state.\n\nTHE JUDGMENT LANE IS DELIBERATELY OFF THE PAGE while its prerequisites are on it: the machines that would serve its model are converging in the fleet lane, and putting the consumer beside the prerequisite would invite starting it early. It is declared, not parked. THE DETERMINISTIC CONFIDENCE LANE IS ON THE PAGE and is not the judgment lane: repository inspection — what exists, who consumes it, what a change may affect — is immediate development infrastructure that must work with no LLM and no Spark provisioned, so it does not wait behind serving. The judgment service depends on it as its grounding packet; the dependency edge carries that order.\n\nA FOCUS IS A DISPLAY CHOICE, NEVER A DISPOSITION, and the difference matters more now than it did when this row selected two lanes. Off the page currently sits real retained work with real remaining boundaries — the namespace closure re-observed on an exact main subject at four of six capabilities established (A–D; the two dependency-projection capabilities remain unavailable under their P2a triggers, closing frontier two — 2026-08-08 execution receipt, superseding the earlier none-admitted wording), the v1 exit with no module yet crossed and no hand-written Rust yet drained, the shell production vertical, the staged alert program, the frozen TypeScript boundary, the instrument adoption, the deployment activation receipt. Hidden means selected against, and nothing here records WHY a hidden lane is paused or what restarts it. That is a missing dimension on the node, not a fact this projection policy can carry: a lane frozen behind infrastructure, a lane draining to merge, and a lane parked because its hypothesis was falsified are three different states, and today all three read identically as absent. Supplying it is a change to the node shape and to what the page counts, and it lands next; until it does, read a hidden lane as retained and unexplained rather than as decided." data roadmap_focus_selection: RoadmapFocus = FocusOnOwners { - label: "the compute fabric + the infrastructure stabilisation lanes + fleet convergence from main", - owners: ["compute", "fleet", "ci-placement", "ci-control", "generated-artifact", "ci-cost", "confidence"], + label: "the compute fabric + the infrastructure stabilisation lanes + fleet convergence from main + the namespace closure", + owners: ["compute", "fleet", "ci-placement", "ci-control", "generated-artifact", "ci-cost", "confidence", "namespace"], } fn projected_roadmap_nodes_for_accepted(accepted: List) -> List { diff --git a/dag/gunbc/stage0_emit_plan_generated.dag b/dag/gunbc/stage0_emit_plan_generated.dag index 25c6bfc3b9f..2f32a08ca8d 100644 --- a/dag/gunbc/stage0_emit_plan_generated.dag +++ b/dag/gunbc/stage0_emit_plan_generated.dag @@ -72,6 +72,7 @@ data generated_stage0_files: List = [ "std_process_termination.rs", "std_realization_schedule.rs", "std_reference_binding_observation.rs", + "std_reference_derived_closure.rs", "std_roster_frontier.rs", "std_serialization.rs", "std_source_annotation.rs", @@ -132,11 +133,15 @@ data generated_stage0_files: List = [ "v1_test_non_ascii_perf_fixture.rs", "v1_tests_claim_caret_parse_smoke_test.rs", "v1_tests_claim_dag_parse_continuation_operator_witness_test.rs", + "v1_tests_claim_import_deletion_bridge_witness.rs", "v1_tests_claim_namespace_reference_derived_closure_production_admissions_witness_test.rs", "v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs", "v1_tests_claim_occurrence_identity_debt_receipt_test.rs", "v1_tests_claim_ordinary_frontend_observation_test.rs", + "v1_tests_claim_parser_declaration_category_matrix_witness_test.rs", + "v1_tests_claim_parser_declaration_matrix_support.rs", "v1_tests_claim_pattern_binder_declaration_node_test.rs", + "v1_tests_claim_reference_derived_closure_witness_test.rs", "v1_tests_claim_required_expr_newline_continuation_test.rs", "v1_tests_claim_v1_annotation_binding_test.rs", "v1_tests_claim_v1_annotation_capture_test.rs", @@ -217,6 +222,7 @@ data generated_stage0_paths: List = [ "src/v1/stage0/src/std_process_termination.rs", "src/v1/stage0/src/std_realization_schedule.rs", "src/v1/stage0/src/std_reference_binding_observation.rs", + "src/v1/stage0/src/std_reference_derived_closure.rs", "src/v1/stage0/src/std_roster_frontier.rs", "src/v1/stage0/src/std_serialization.rs", "src/v1/stage0/src/std_source_annotation.rs", @@ -277,11 +283,15 @@ data generated_stage0_paths: List = [ "src/v1/stage0/src/v1_test_non_ascii_perf_fixture.rs", "src/v1/stage0/src/v1_tests_claim_caret_parse_smoke_test.rs", "src/v1/stage0/src/v1_tests_claim_dag_parse_continuation_operator_witness_test.rs", + "src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs", "src/v1/stage0/src/v1_tests_claim_namespace_reference_derived_closure_production_admissions_witness_test.rs", "src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs", "src/v1/stage0/src/v1_tests_claim_occurrence_identity_debt_receipt_test.rs", "src/v1/stage0/src/v1_tests_claim_ordinary_frontend_observation_test.rs", + "src/v1/stage0/src/v1_tests_claim_parser_declaration_category_matrix_witness_test.rs", + "src/v1/stage0/src/v1_tests_claim_parser_declaration_matrix_support.rs", "src/v1/stage0/src/v1_tests_claim_pattern_binder_declaration_node_test.rs", + "src/v1/stage0/src/v1_tests_claim_reference_derived_closure_witness_test.rs", "src/v1/stage0/src/v1_tests_claim_required_expr_newline_continuation_test.rs", "src/v1/stage0/src/v1_tests_claim_v1_annotation_binding_test.rs", "src/v1/stage0/src/v1_tests_claim_v1_annotation_capture_test.rs", diff --git a/dag/gunbc/type_reference_binding_context.dag b/dag/gunbc/type_reference_binding_context.dag index d6be38f2a6a..a2bdb1e2fd3 100644 --- a/dag/gunbc/type_reference_binding_context.dag +++ b/dag/gunbc/type_reference_binding_context.dag @@ -67,6 +67,7 @@ fn occurrence_binding_inputs_from_module_transport( module_path: module_path, containment: declaration.containment, grounding: NamespaceStructuralRootExposure, + category: declaration.category, ), }, ], acc), diff --git a/dag/std/occurrence_binding_candidates.dag b/dag/std/occurrence_binding_candidates.dag index 2db5bf0762d..55f036acbc8 100644 --- a/dag/std/occurrence_binding_candidates.dag +++ b/dag/std/occurrence_binding_candidates.dag @@ -2,12 +2,14 @@ module std.occurrence_binding_candidates import std.dissolution { DissolutionCondition, unbound_dissolution } -import std.types { String, Int, Bool, List, NonEmptyStr, CommitSha, FilePath } +import std.types { String, Int, Bool, List, NonEmptyStr, CommitSha, FilePath, SourceSpan } import std.algebra { FreeMonoid, Empty, Cons } import std.occurrence_binding { OccurrenceBound, OccurrenceUnbound, OccurrenceAmbiguous, + AmbiguousBindingCandidates, + BindingCandidate, } import std.occurrence_identity { OccurrenceId, @@ -24,8 +26,13 @@ import std.occurrence_identity { OccurrenceCategory, TypeOccurrence, occurrence_category_clause_e_dependency_inducing_verdict, + occurrence_category_module_scope_exposure_verdict, + OccurrenceCategoryModuleScopeExposed, + OccurrenceCategoryModuleScopeNotExposed, OccurrenceCategoryClauseEDependencyInducing, OccurrenceCategoryClauseEDependencyNotInducing, + ReferencePath, + ReferencePathSegment, OccurrenceContainmentPath, occurrence_containment_path_is_prefix_of, occurrence_containment_paths_equal, @@ -189,12 +196,11 @@ fn declaration_exposure_eq( } } -data declaration_exposure_from_containment_note: String = "Single authority for declaration exposure from containment prefix shape and module path. OccurrenceCategory is never consulted. DeclarationExposureGrounding selects how module-root and direct-module-child shapes map to DeclarationExposure: NamespaceStructuralRootExposure (N2 resolve path — empty ancestors => RootExposure); ModuleLocalMemberExposure (in-module binding walk — module-root and direct-module-child => ModuleExposure); CrossFileProviderExportedExposure (cross-file provider declarations exported to consumers — module-root and direct-module-child => RootExposure at derivation, never via a post-hoc upgrade). Nested declarations always derive LexicalExposure via occurrence_containment_parent_scope." +data declaration_exposure_from_containment_note: String = "Single authority for declaration exposure from containment prefix shape and module path. OccurrenceCategory is never consulted. DeclarationExposureGrounding selects how module-root and direct-module-child shapes map to DeclarationExposure: NamespaceStructuralRootExposure (N2 resolve path — empty ancestors => RootExposure); ModuleLocalMemberExposure (every ordinary module member, in-file or cross-file — module-root and direct-module-child => ModuleExposure). Nested declarations always derive LexicalExposure via occurrence_containment_parent_scope. CrossFileProviderExportedExposure (module member => RootExposure for consumers in other files) was DELETED 2026-08-13: it made the whole source-root universe the visibility set, so any globally unique leaf bound bare from anywhere and the accepted declaration for one file changed with which file was the entry. A module member is exposed as a member and reached by projection (projected_reference_candidates_note); the universe is the declaration denominator, never the visibility set." type DeclarationExposureGrounding = NamespaceStructuralRootExposure | ModuleLocalMemberExposure - | CrossFileProviderExportedExposure fn occurrence_containment_parent_scope( ancestors: FreeMonoid @@ -211,9 +217,30 @@ fn occurrence_containment_parent_scope( } } +// A declaration nested inside another declaration is lexically scoped UNLESS its category says it is +// a module member. Containment alone cannot separate the two: a constructor and a field are both +// children of a type node, so both derive ancestors [module, type], yet a constructor is reachable at +// module scope and a field is not. The category authority already answers exactly that question, so +// the nested case asks it rather than guessing from shape. This is not category substituted FOR +// exposure -- the decision site still reads only DeclarationExposure -- it is the grounding being +// chosen at derivation, which is what the exposure authority requires. Measured before: a module-scope +// reference to a constructor did not bind, because the constructor's exposing scope was the type body. +fn declaration_exposure_for_nested( + module_path: NonEmptyStr, + exposing_scope: OccurrenceContainmentPath, + category: OccurrenceCategory +) -> DeclarationExposure { + match occurrence_category_module_scope_exposure_verdict(category: category) { + OccurrenceCategoryModuleScopeExposed => ModuleExposure { module: module_path } + OccurrenceCategoryModuleScopeNotExposed { category: _ } => + LexicalExposure { exposing_scope: exposing_scope } + } +} + fn declaration_exposure_module_local_member( module_path: NonEmptyStr, - containment: OccurrenceContainmentPath + containment: OccurrenceContainmentPath, + category: OccurrenceCategory ) -> DeclarationExposure { match occurrence_containment_parent_scope(ancestors: containment.ancestors) { Absent => @@ -223,7 +250,11 @@ fn declaration_exposure_module_local_member( Empty => ModuleExposure { module: module_path } Cons { head: _, tail: _ } => - LexicalExposure { exposing_scope: exposing_scope } + declaration_exposure_for_nested( + module_path: module_path, + exposing_scope: exposing_scope, + category: category, + ) } } } @@ -231,6 +262,7 @@ fn declaration_exposure_module_local_member( fn declaration_exposure_namespace_structural_root( module_path: NonEmptyStr, containment: OccurrenceContainmentPath, + category: OccurrenceCategory, ) -> DeclarationExposure { match occurrence_containment_parent_scope( ancestors: containment.ancestors, @@ -244,7 +276,11 @@ fn declaration_exposure_namespace_structural_root( ModuleExposure { module: module_path } Cons { head: _, tail: _ } => - LexicalExposure { exposing_scope: exposing_scope } + declaration_exposure_for_nested( + module_path: module_path, + exposing_scope: exposing_scope, + category: category, + ) } } } @@ -252,29 +288,22 @@ fn declaration_exposure_namespace_structural_root( fn declaration_exposure_from_containment( module_path: NonEmptyStr, containment: OccurrenceContainmentPath, - grounding: DeclarationExposureGrounding + grounding: DeclarationExposureGrounding, + category: OccurrenceCategory ) -> DeclarationExposure { match grounding { NamespaceStructuralRootExposure => declaration_exposure_namespace_structural_root( module_path: module_path, containment: containment, + category: category, ) ModuleLocalMemberExposure => declaration_exposure_module_local_member( module_path: module_path, containment: containment, + category: category, ) - CrossFileProviderExportedExposure => - match declaration_exposure_module_local_member( - module_path: module_path, - containment: containment, - ) { - ModuleExposure { module: _ } => RootExposure - LexicalExposure { exposing_scope: exposing_scope } => - LexicalExposure { exposing_scope: exposing_scope } - RootExposure => RootExposure - } } } @@ -643,11 +672,62 @@ fn declaration_exposed_on_reference_chain( } } +data head_reference_names_a_module_note: String = "THE HEAD OF A PROJECTION IS A MODULE, and at this grain the parser cannot say so. An expression path p.first arrives as a bare reference p plus a field projection, not as one dotted occurrence the way a TYPE path does, because the dot IS containment projection and the expression form is the structurally faithful one. So the head arrives spelled as an ordinary value reference (ExprVar, LexicalValueOccurrence) while the thing it names is a module declaration (NamespaceSegmentOccurrence), which is why the value-reference arm admits that pair: the reference site genuinely does not know whether a bare name is a local binder, a module member or a module, and the reference site is not where that is decided. A module declaration is supplied to a bare reference WITHOUT the on-chain exposure filter, deliberately: module-sibling exposure gates on same-module plus source order, both of which are within-file relations, and a module name is visible across files by design. Cardinality still decides -- a name that is both a local binder and a module segment supplies both and folds to Ambiguous. DISSOLVE-ON: one ReferencePath carrier \{head, projections\} that both syntactic positions feed, at which point the parser mints the head as a namespace reference directly and this category admission stops being needed. WHAT THIS DOES NOT DO: it binds the HEAD, which is what a file DEPENDENCY needs -- the provider file follows from the module declaration's own file. It does not descend to the member, so a bound head with a missing or ambiguous member is not yet distinguished here; that belongs to the resolver cutover, not to dependency derivation." + +data projected_reference_candidates_note: String = "A reference whose authored text carries a qualifier (live_tree.LiveTreeDisposition) is ONE occurrence whose authored_name is the whole dotted path — the parser already mints it that way, so nothing new is minted here; what this adds is the resolution rule the flat declarations_by_name lookup never had. The leaf segment selects the same-spelled bucket; the qualifier selects which declaring module may answer, by segment-suffix over the declaration's own OccurrenceModulePathRow (tmp.q.b is named by b or by q.b or in full). Exposure for a projected reference is ModuleExposure for exactly that module and NOTHING else: RootExposure does not answer a qualified name (a root declaration is not a member of the named module) and the module-sibling ordinal gate is deliberately NOT applied, because source order is a within-file relation and two files have no authored order between them. Cardinality stays downstream: if two modules end in the same segment both declarations are supplied and the population folds to Ambiguous, which is the correct refusal rather than a nearest-match heuristic (DESIGN section 4 — a heuristic is never necessary in a closed system)." + +fn reference_qualifier_of_authored_name(name: String) -> String? { + match string_contains(name, ".") { + false => none + true => + match last(name |> split(delimiter: ".")) { + Absent => none + Present { value: leaf } => + Present { + value: substring( + s: name, + start: 0, + end: string_length(name) - string_length(leaf) - 1, + ), + } + } + } +} + +fn module_named_by_qualifier(module: NonEmptyStr, qualifier: String) -> Bool { + (module as String) == qualifier + || ends_with(s: module as String, suffix: concat(".", qualifier)) +} + +fn declaration_projected_from_named_module( + declaration: DeclarationOccurrence, + qualifier: String, + exposure_by_occurrence: Map +) -> Bool { + match exposure_of_occurrence( + exposure_by_occurrence: exposure_by_occurrence, + occurrence: declaration.occurrence, + ) { + Absent => false + Present { value: exposure } => + match exposure { + ModuleExposure { module: exposure_module } => + module_named_by_qualifier( + module: exposure_module, + qualifier: qualifier, + ) + LexicalExposure { exposing_scope: _ } => false + RootExposure => false + } + } +} + data occurrence_candidate_index_note: String = "OccurrenceCandidateIndex is built exactly once per transport (occurrence_candidate_index_build). declarations_by_name buckets every declaration by its authored spelling so a single reference's candidate lookup is a map_get plus a scan of only the same-spelled declarations, never a rescan of the whole declaration population -- the O(declarations x references) shape the pre-strip candidate scan had. entries_by_id is the same validated lookup authority std.occurrence_binding_resolve builds internally; it is retained here only to read authored_name, never to re-decide cardinality." type OccurrenceCandidateIndex { entries_by_id: Map declarations_by_name: Map> + module_declarations_by_segment: Map> module_by_occurrence: Map exposure_by_occurrence: Map order_by_occurrence: Map @@ -718,6 +798,54 @@ fn declarations_by_name_build( } } +data module_declarations_by_segment_note: String = "A module declaration carries its WHOLE dotted path as one authored name (v1.compiler.parse parse_module stamps the module node once, name: mod_name), so a head reference spelled with the last segment alone -- the p in p.first -- finds nothing in declarations_by_name, which is keyed by authored spelling. This index buckets module declarations by their last segment so a head reference has something to resolve against, and it is keyed by segment rather than by full path because the ratified migration form is the SHORTEST qualifying container path, not the path from the root. Two modules ending in the same segment therefore supply two candidates and fold to Ambiguous downstream -- the refusal, never a nearest-match. Only NamespaceSegmentOccurrence declarations enter: the category is read from the DeclarationOccurrence rather than inferred from the presence of a dot in the text, so an ordinary declaration whose name happened to contain a dot could not impersonate a module." + +fn module_declaration_last_segment(authored_name: String) -> String { + match last(authored_name |> split(delimiter: ".")) { + Absent => authored_name + Present { value: segment } => segment + } +} + +fn module_declarations_by_segment_step( + acc: Map>, + entries_by_id: Map, + declaration: DeclarationOccurrence +) -> Map> { + match declaration.category { + NamespaceSegmentOccurrence => + match map_get(entries_by_id, declaration.occurrence.value) { + Absent => acc + Present { value: entry } => + let segment = module_declaration_last_segment( + authored_name: entry.projection.authored_name, + ) + match map_get(acc, segment) { + Absent => map_insert(acc, segment, [declaration]) + Present { value: existing } => + map_insert(acc, segment, concat([declaration], existing)) + } + } + _ => acc + } +} + +fn module_declarations_by_segment_build( + entries_by_id: Map, + declarations: List +) -> Map> { + fold( + declarations, + init: empty_map(), + f: (acc, declaration) => + module_declarations_by_segment_step( + acc: acc, + entries_by_id: entries_by_id, + declaration: declaration, + ), + ) +} + type OccurrenceCandidateIndexBuild = OccurrenceCandidateIndexReady { index: OccurrenceCandidateIndex } | OccurrenceCandidateIndexTransportRefused { refusal: OccurrenceTransportRefusal } @@ -786,6 +914,10 @@ fn occurrence_candidate_index_build( index: OccurrenceCandidateIndex { entries_by_id: validated.entries_by_id, declarations_by_name: by_name, + module_declarations_by_segment: module_declarations_by_segment_build( + entries_by_id: validated.entries_by_id, + declarations: validated.declarations, + ), module_by_occurrence: module_path_build.module_by_occurrence, exposure_by_occurrence: exposure_build.exposure_by_occurrence, order_by_occurrence: order_build.order_by_occurrence, @@ -810,18 +942,51 @@ fn candidate_occurrence_ids_for_reference( match map_get(index.entries_by_id, reference.occurrence.value) { Absent => [] Present { value: entry } => - match map_get(index.declarations_by_name, entry.projection.authored_name) { - Absent => [] - Present { value: same_spelling } => - reverse(same_spelling) - |> filter(declaration => declaration_exposed_on_reference_chain( - declaration: declaration, - reference: reference, - module_by_occurrence: index.module_by_occurrence, - exposure_by_occurrence: index.exposure_by_occurrence, - order_by_occurrence: index.order_by_occurrence, - )) - |> map(declaration => declaration.occurrence) + match reference_qualifier_of_authored_name( + name: entry.projection.authored_name, + ) { + Absent => + let on_chain = + match map_get(index.declarations_by_name, entry.projection.authored_name) { + Absent => [] + Present { value: same_spelling } => + reverse(same_spelling) + |> filter(declaration => declaration_exposed_on_reference_chain( + declaration: declaration, + reference: reference, + module_by_occurrence: index.module_by_occurrence, + exposure_by_occurrence: index.exposure_by_occurrence, + order_by_occurrence: index.order_by_occurrence, + )) + |> map(declaration => declaration.occurrence) + } + let named_modules = + match map_get( + index.module_declarations_by_segment, + entry.projection.authored_name, + ) { + Absent => [] + Present { value: modules } => + reverse(modules) |> map(declaration => declaration.occurrence) + } + concat(on_chain, named_modules) + Present { value: qualifier } => + let leaf = substring( + s: entry.projection.authored_name, + start: string_length(qualifier) + 1, + end: string_length(entry.projection.authored_name), + ) + match map_get(index.declarations_by_name, leaf) { + Absent => [] + Present { value: same_spelling } => + reverse(same_spelling) + |> filter(declaration => declaration_projected_from_named_module( + declaration: declaration, + qualifier: qualifier, + exposure_by_occurrence: index.exposure_by_occurrence, + )) + |> map(declaration => declaration.occurrence) + } } } } @@ -947,7 +1112,7 @@ fn bound_reference_population_fold_step( more_failures_reversed: concat([projection], acc.more_failures_reversed), } } - ReferenceBindingProjectionAmbiguous { occurrence: _ } => + ReferenceBindingProjectionAmbiguous { occurrence: _, candidates: _ } => match acc.first_failure { Absent => BoundReferencePopulationBuild { @@ -1542,6 +1707,652 @@ fn reference_derived_dependency_projection( } } +data reference_dependency_admission_note: String = "THE DEPENDENCY-INDUCING QUESTION IS BROADER THAN CLAUSE (e), and this authority composes the two rather than widening either. + +Clause (e) is one clause of one contract, and its verdict is deliberately left alone: TypeOccurrence is still OccurrenceCategoryClauseEDependencyNotInducing, because reclassifying it there to reuse the filter would make the clause say something its own contract does not, and every consumer of that clause would silently inherit the change. What is true instead is that a type reference induces a file dependency by a DIFFERENT route -- N2's typed entry, whose category guard is part of its contract -- so the admission surface has one arm per route and an excluded arm that carries the category it excluded. + +Composition, not a second table: this reads the clause-(e) verdict first and only inspects the category inside its NotInducing arm. A parallel match over the seven categories would be a second classification of the same coproduct, so clause (e) remains the authority for its own question and this authority answers only what it adds. + +WHY THE PRODUCTION PROJECTION DOES NOT YET USE IT, stated because a surface with no consumer is exactly what this repository calls coverage by illusion. Admitting type references makes a parser-produced annotation such as `Int` enter the population, and its declaration is not in an assembled closure that was never given std.types -- so the population refuses, WHOLE, by the totality law. That refusal is correct and is the point: it is the demand the loader has to satisfy, named at the exact occurrence, and it is the loader work list rather than a skip. Silently dropping an unbindable type reference to keep the projection green would be the empty-observation narrow -- rendering `could not see the provider` as `nothing is depended on` -- which is strictly worse than the widen DESIGN section 5 already forbids. + +So this lands as the joined walk plus its witnesses, and the production switch at reference_derived_dependency_projection_for_assembled_closure happens when the loader can supply the providers the demand names. DISSOLVE-ON: that switch, at which point reference_derived_dependency_binding_references has no caller and deletes." + +type ReferenceDependencyAdmission + = ReferenceDependencyAdmittedClauseE + | ReferenceDependencyAdmittedTypeReference + | ReferenceDependencyExcluded { category: OccurrenceCategory } + +data module_member_projection_note: String = "THE HEAD-ONLY WIDEN, CLOSED. An expression path p.first is a FieldOccurrence reference whose base is its child, and FieldOccurrence is excluded from clause-E dependency inducement -- correctly, for a record field access. The exclusion was applied to the DEPENDENCY question and silently answered the BINDING question too: an excluded reference is never added to the required population, so it never had to resolve. Measured consequence, reproduced before the repair: p.no_such_member settled with one edge against a provider that declares no such member, because the edge came from the head binding to the module while the member was never asked about. A projection off a module head is not a record field access, and the difference is decidable here rather than at the category: the base resolves to a NamespaceSegmentOccurrence or it does not. When it does, the segment must resolve among that module's own members, and the dependency is attributed to the TERMINAL declaration -- which is what a file dependency actually rests on -- rather than to the module head that merely names where to look." + +data reference_path_producer_note: String = "TWO SPELLINGS IN, ONE PATH OUT. The dotted spelling is decided first because it is decidable from the authored text alone: a name carrying a dot is one occurrence covering the whole path, so its head is the first segment and every later segment inherits the one span the parser allocated -- coarse, and honestly so, because there is no second occurrence to carry a narrower one. Otherwise the reference is walked DOWN its containment children: a projection segment has exactly one child (its receiver), so more than one child is not a longer path but a shape this producer does not model, and it refuses rather than picking one. A reference with no child and no dot is a bare name, which is a path of zero segments -- the ordinary case, not a special one. THE ORDER: walking down yields segments outermost-first, so the accumulator is built head-adjacent-last and reversed once at the end, never appended per step." + +type ReferencePathBuild + = ReferencePathBuilt { path: ReferencePath } + | ReferencePathRefused { reason: ReferencePathRefusal } + +type ReferencePathRefusal + = ReferencePathOccurrenceNotIndexed { occurrence: OccurrenceId } + | ReferencePathBranchingProjection { occurrence: OccurrenceId, child_count: Int } + | ReferencePathEmptySegmentText { occurrence: OccurrenceId } + | ReferencePathDescentUnbounded { occurrence: OccurrenceId } + +fn reference_list_count(xs: List) -> Int { + length(xs) +} + +fn reference_children( + references: List, + reference: ReferenceOccurrence +) -> List { + reverse(fold(references, init: Empty, f: (acc, candidate) => + match reverse(candidate.containment.ancestors) { + Empty => acc + Cons { head: parent, tail: _ } => + match parent.value == reference.occurrence.value { + true => Cons { head: candidate, tail: acc } + false => acc + } + })) +} + +fn reference_authored_name( + index: OccurrenceCandidateIndex, + reference: ReferenceOccurrence +) -> String? { + match map_get(index.entries_by_id, reference.occurrence.value) { + Absent => none + Present { value: entry } => Present { value: entry.projection.authored_name } + } +} + +fn reference_path_segments_from_dotted( + name: String, + span: SourceSpan +) -> List { + fold(name |> split(delimiter: "."), init: Empty, f: (acc, segment) => + Cons { head: ReferencePathSegment { name: segment, diagnostic_span: span, occurrence: none }, tail: acc }) +} + +data reference_path_module_prefix_note: String = "THE HEAD OF A DOTTED PATH IS A MODULE PATH, NOT A SEGMENT, and an earlier revision of this producer got that wrong in a way worth recording because it looked correct against the corpus. It split at EVERY dot and took the FIRST segment as the head, which happens to work for the import-era spelling live_tree.LiveTreeDisposition -- one qualifier segment, one member -- and fails for every other shape. A fully qualified std.types.NonEmptyStr yields head std, which names no module, so it refused at the head; and a three-segment live_tree.LiveTreeDisposition.ReadsLiveTree refused the same way, which would have read as multi-segment descent being unimplemented when the real defect was the split. The rule is instead the LONGEST PREFIX that names a module: segments are consumed into the head while they still name one, and everything after is descent. That serves both spellings from one rule -- the exact-path match answers a fully qualified reference and the suffix match answers a shortest-qualifier one, because module_named_by_qualifier already admits both -- so the qualification policy is a corpus-migration question rather than a producer question, which is the opposite of what a one-segment split implied." + +fn module_declaration_named_by_qualifier( + index: OccurrenceCandidateIndex, + declaration: DeclarationOccurrence, + qualifier: String +) -> Bool { + match map_get(index.entries_by_id, declaration.occurrence.value) { + Absent => false + Present { value: entry } => + (entry.projection.authored_name == qualifier) + || ends_with(s: entry.projection.authored_name, suffix: concat(".", qualifier)) + } +} + +fn module_declared_by_qualifier(index: OccurrenceCandidateIndex, qualifier: String) -> Bool { + match map_get( + index.module_declarations_by_segment, + module_declaration_last_segment(authored_name: qualifier), + ) { + Absent => false + Present { value: modules } => + fold(modules, init: false, f: (found, declaration) => + found || module_declaration_named_by_qualifier( + index: index, + declaration: declaration, + qualifier: qualifier, + )) + } +} + +type ModulePrefixScan { + taken: Int + prefix: String + best: Int +} + +fn module_prefix_scan_step( + index: OccurrenceCandidateIndex, + acc: ModulePrefixScan, + segment: String +) -> ModulePrefixScan { + let grown = prefix_append(prefix: acc.prefix, taken: acc.taken, segment: segment) + let taken = acc.taken + 1 + match module_declared_by_qualifier(index: index, qualifier: grown) { + true => ModulePrefixScan { taken: taken, prefix: grown, best: taken } + false => ModulePrefixScan { taken: taken, prefix: grown, best: acc.best } + } +} + +fn module_prefix_match_length(index: OccurrenceCandidateIndex, segments: List) -> Int { + fold(segments, init: ModulePrefixScan { taken: 0, prefix: "", best: 0 }, f: (acc, segment) => + module_prefix_scan_step(index: index, acc: acc, segment: segment)).best +} + +fn joined_prefix_step(acc: ModulePrefixScan, segment: String) -> ModulePrefixScan { + match acc.taken >= acc.best { + true => ModulePrefixScan { taken: acc.taken + 1, prefix: acc.prefix, best: acc.best } + false => ModulePrefixScan { + taken: acc.taken + 1, + prefix: prefix_append(prefix: acc.prefix, taken: acc.taken, segment: segment), + best: acc.best, + } + } +} + +fn prefix_append(prefix: String, taken: Int, segment: String) -> String { + match taken == 0 { + true => segment + false => concat(prefix, concat(".", segment)) + } +} + +fn joined_prefix(segments: List, count: Int) -> String { + fold( + segments, + init: ModulePrefixScan { taken: 0, prefix: "", best: count }, + f: joined_prefix_step + ).prefix +} + +fn reference_path_segment_of(name: String, span: SourceSpan) -> ReferencePathSegment { + ReferencePathSegment { name: name, diagnostic_span: span, occurrence: none } +} + +fn reference_path_tail_segments( + segments: List, + head_len: Int, + span: SourceSpan +) -> List { + segments |> skip(n: head_len) |> map(segment => reference_path_segment_of(name: segment, span: span)) +} + +fn reference_path_with_module_head( + reference: ReferenceOccurrence, + segments: List, + head_len: Int +) -> ReferencePathBuild { + ReferencePathBuilt { + path: ReferencePath { + head_name: joined_prefix(segments: segments, count: head_len), + head_span: reference.diagnostic_span, + head_occurrence: reference.occurrence, + segments: reference_path_tail_segments( + segments: segments, + head_len: head_len, + span: reference.diagnostic_span, + ), + } + } +} + +fn reference_path_with_first_segment_head( + reference: ReferenceOccurrence, + segments: List +) -> ReferencePathBuild { + match segments { + Empty => ReferencePathRefused { reason: ReferencePathEmptySegmentText { occurrence: reference.occurrence } } + Cons { head: first_segment, tail: _ } => + reference_path_with_module_head(reference: reference, segments: segments, head_len: 1) + } +} + +fn reference_path_from_dotted_name( + index: OccurrenceCandidateIndex, + reference: ReferenceOccurrence, + name: String +) -> ReferencePathBuild { + let segments = name |> split(delimiter: ".") + let head_len = module_prefix_match_length(index: index, segments: segments) + match head_len == 0 { + true => reference_path_with_first_segment_head(reference: reference, segments: segments) + false => reference_path_with_module_head(reference: reference, segments: segments, head_len: head_len) + } +} + +fn reference_path_descend( + index: OccurrenceCandidateIndex, + references: List, + reference: ReferenceOccurrence, + segments: List, + fuel: Int +) -> ReferencePathBuild { + match reference_authored_name(index: index, reference: reference) { + Absent => ReferencePathRefused { reason: ReferencePathOccurrenceNotIndexed { occurrence: reference.occurrence } } + Present { value: name } => + match reference_children(references: references, reference: reference) { + Empty => ReferencePathBuilt { + path: ReferencePath { + head_name: name, + head_span: reference.diagnostic_span, + head_occurrence: reference.occurrence, + segments: reverse(segments), + } + } + Cons { head: child, tail: siblings } => + match siblings { + Cons { head: _, tail: _ } => + ReferencePathRefused { + reason: ReferencePathBranchingProjection { + occurrence: reference.occurrence, + child_count: 1 + reference_list_count(xs: siblings), + } + } + Empty => + match fuel <= 0 { + true => ReferencePathRefused { reason: ReferencePathDescentUnbounded { occurrence: reference.occurrence } } + false => reference_path_descend( + index: index, + references: references, + reference: child, + segments: Cons { + head: ReferencePathSegment { + name: name, + diagnostic_span: reference.diagnostic_span, + occurrence: Present { value: reference.occurrence }, + }, + tail: segments, + }, + fuel: fuel - 1, + ) + } + } + } + } +} + +fn reference_path_of( + index: OccurrenceCandidateIndex, + references: List, + reference: ReferenceOccurrence +) -> ReferencePathBuild { + match reference_authored_name(index: index, reference: reference) { + Absent => ReferencePathRefused { reason: ReferencePathOccurrenceNotIndexed { occurrence: reference.occurrence } } + Present { value: name } => + match string_contains(name, ".") { + true => reference_path_from_dotted_name(index: index, reference: reference, name: name) + false => reference_path_descend( + index: index, + references: references, + reference: reference, + segments: Empty, + fuel: reference_list_count(xs: references), + ) + } + } +} + +data reference_path_not_a_namespace_projection_note: String = "A DOT IS NOT ALWAYS A NAMESPACE STEP, and the difference is a THIRD STATE rather than a refusal. x.field on an ordinary record binder is a field access: it induces no cross-file dependency of its own (the dependency was already induced by the reference to x's TYPE), and its member is a field of a type rather than a member of a module. Answering it with SegmentUnbound would be the fabricated-plausible-output failure pointed at a well-formed program -- a correct field access reported as an unresolved name. Answering it with a resolved path would be worse, since it would attribute a dependency to whatever declaration happened to match the field spelling. So the head's own category decides: only a NamespaceSegmentOccurrence head descends, and any other head with segments present returns NotANamespaceProjection carrying the head declaration, which tells the caller this reference is somebody else's question. It is deliberately NOT an error arm and deliberately NOT success: the caller must handle three outcomes, because there are three." + +data reference_path_resolution_note: String = "ONE HEAD BINDING, THEN CONTAINMENT. The head is resolved exactly once, through the same on-chain-plus-named-modules candidate rule an ordinary bare reference uses -- there is no second, segment-index reconstruction of the head beside it, because two derivations of one fact are two chances to disagree and the disagreement would be silent. Every later segment is a CONTAINMENT CHILD step: the declarations whose containment parent is the previous accepted declaration and whose authored name is the segment. That step is uniform for the first segment and the tenth, and it needed no qualifier suffix-match, because a module member's containment parent is measurably the module declaration occurrence itself. THE CARDINALITY RULE IS THE SAME AT EVERY STEP AND IS NOT SPECIAL-CASED: zero children Unbound, one child descend, more than one Ambiguous naming every candidate -- never a nearest match, never first-wins. WHAT MAKES THIS A WALL RATHER THAN A CHECK: the only value that can carry a terminal declaration is produced by consuming every segment, so a head that bound while a segment did not has no representation as a resolved path. A caller cannot read a provider file off a partial descent, because a partial descent yields a refusal and refusals carry no declaration." + +type ReferencePathResolution + = ReferencePathResolved { terminal: DeclarationOccurrence } + | ReferencePathHeadUnbound { head_name: String, span: SourceSpan } + | ReferencePathHeadAmbiguous { head_name: String, span: SourceSpan, candidates: List } + | ReferencePathSegmentUnbound { segment: ReferencePathSegment, parent: OccurrenceId } + | ReferencePathSegmentAmbiguous { segment: ReferencePathSegment, parent: OccurrenceId, candidates: List } + | ReferencePathBuildRefused { reason: ReferencePathRefusal } + | ReferencePathNotANamespaceProjection { head: DeclarationOccurrence } + +fn declaration_occurrence_ids(xs: List) -> List { + reverse(fold(xs, init: Empty, f: (acc, declaration) => + Cons { head: declaration.occurrence, tail: acc })) +} + +fn head_candidate_declarations( + index: OccurrenceCandidateIndex, + reference: ReferenceOccurrence, + head_name: String +) -> List { + let on_chain = + match map_get(index.declarations_by_name, head_name) { + Absent => [] + Present { value: same_spelling } => + reverse(same_spelling) + |> filter(declaration => declaration_exposed_on_reference_chain( + declaration: declaration, + reference: reference, + module_by_occurrence: index.module_by_occurrence, + exposure_by_occurrence: index.exposure_by_occurrence, + order_by_occurrence: index.order_by_occurrence, + )) + } + let named_modules = + match map_get( + index.module_declarations_by_segment, + module_declaration_last_segment(authored_name: head_name), + ) { + Absent => [] + Present { value: modules } => + reverse(modules) + |> filter(declaration => module_declaration_named_by_qualifier( + index: index, + declaration: declaration, + qualifier: head_name, + )) + } + concat(on_chain, named_modules) +} + +fn declaration_children_named( + transport: OccurrenceTransport, + index: OccurrenceCandidateIndex, + parent: OccurrenceId, + name: String +) -> List { + reverse(fold(transport.declarations, init: Empty, f: (acc, declaration) => + match reverse(declaration.containment.ancestors) { + Empty => acc + Cons { head: declared_parent, tail: _ } => + match declared_parent.value == parent.value { + false => acc + true => + match map_get(index.entries_by_id, declaration.occurrence.value) { + Absent => acc + Present { value: entry } => + match entry.projection.authored_name == name { + true => Cons { head: declaration, tail: acc } + false => acc + } + } + } + })) +} + +fn reference_path_descend_segments( + transport: OccurrenceTransport, + index: OccurrenceCandidateIndex, + current: DeclarationOccurrence, + segments: List +) -> ReferencePathResolution { + match segments { + Empty => ReferencePathResolved { terminal: current } + Cons { head: segment, tail: rest } => + match declaration_children_named( + transport: transport, + index: index, + parent: current.occurrence, + name: segment.name, + ) { + Empty => ReferencePathSegmentUnbound { segment: segment, parent: current.occurrence } + Cons { head: only, tail: others } => + match others { + Cons { head: _, tail: _ } => + ReferencePathSegmentAmbiguous { + segment: segment, + parent: current.occurrence, + candidates: Cons { head: only, tail: others }, + } + Empty => reference_path_descend_segments( + transport: transport, + index: index, + current: only, + segments: rest, + ) + } + } + } +} + +fn resolve_reference_path( + transport: OccurrenceTransport, + index: OccurrenceCandidateIndex, + reference: ReferenceOccurrence +) -> ReferencePathResolution { + match reference_path_of( + index: index, + references: transport.references, + reference: reference, + ) { + ReferencePathRefused { reason: reason } => ReferencePathBuildRefused { reason: reason } + ReferencePathBuilt { path: path } => + match head_candidate_declarations( + index: index, + reference: reference, + head_name: path.head_name, + ) { + Empty => ReferencePathHeadUnbound { head_name: path.head_name, span: path.head_span } + Cons { head: only, tail: others } => + match others { + Cons { head: _, tail: _ } => + ReferencePathHeadAmbiguous { + head_name: path.head_name, + span: path.head_span, + candidates: declaration_occurrence_ids( + xs: Cons { head: only, tail: others }, + ), + } + Empty => + match path.segments { + Empty => ReferencePathResolved { terminal: only } + Cons { head: _, tail: _ } => + match only.category { + NamespaceSegmentOccurrence => reference_path_descend_segments( + transport: transport, + index: index, + current: only, + segments: path.segments, + ) + _ => ReferencePathNotANamespaceProjection { head: only } + } + } + } + } + } +} + +fn reference_base_child( + references: List, + reference: ReferenceOccurrence +) -> ReferenceOccurrence? { + fold(references, init: none, f: (found, candidate) => + match found { + Present { value: _ } => found + Absent => + match reverse(candidate.containment.ancestors) { + Empty => none + Cons { head: parent, tail: _ } => + match parent.value == reference.occurrence.value { + true => Present { value: candidate } + false => none + } + } + }) +} + +fn module_declaration_named_by_reference( + index: OccurrenceCandidateIndex, + base: ReferenceOccurrence +) -> DeclarationOccurrence? { + match map_get(index.entries_by_id, base.occurrence.value) { + Absent => none + Present { value: entry } => + match map_get(index.module_declarations_by_segment, entry.projection.authored_name) { + Absent => none + Present { value: modules } => + match modules { + Empty => none + Cons { head: only, tail: rest } => + match rest { + Empty => Present { value: only } + Cons { head: _, tail: _ } => none + } + } + } + } +} + +fn module_member_candidate_ids( + index: OccurrenceCandidateIndex, + provider: DeclarationOccurrence, + segment_name: String +) -> List { + match map_get(index.entries_by_id, provider.occurrence.value) { + Absent => [] + Present { value: provider_entry } => + match map_get(index.declarations_by_name, segment_name) { + Absent => [] + Present { value: same_spelling } => + reverse(same_spelling) + |> filter(declaration => declaration_projected_from_named_module( + declaration: declaration, + qualifier: provider_entry.projection.authored_name, + exposure_by_occurrence: index.exposure_by_occurrence, + )) + |> map(declaration => declaration.occurrence) + } + } +} + +fn reference_dependency_admission_for_category( + category: OccurrenceCategory +) -> ReferenceDependencyAdmission { + match occurrence_category_clause_e_dependency_inducing_verdict(category: category) { + OccurrenceCategoryClauseEDependencyInducing => ReferenceDependencyAdmittedClauseE + OccurrenceCategoryClauseEDependencyNotInducing { category: excluded } => + match excluded { + TypeOccurrence => ReferenceDependencyAdmittedTypeReference + FieldOccurrence => ReferenceDependencyExcluded { category: excluded } + MethodOccurrence => ReferenceDependencyExcluded { category: excluded } + LexicalValueOccurrence => ReferenceDependencyExcluded { category: excluded } + CallableOccurrence => ReferenceDependencyExcluded { category: excluded } + ConstructorOccurrence => ReferenceDependencyExcluded { category: excluded } + NamespaceSegmentOccurrence => ReferenceDependencyExcluded { category: excluded } + ModuleValueOccurrence => ReferenceDependencyExcluded { category: excluded } + } + } +} + +data resolve_type_reference_via_structural_candidates_note: String = "The N2 typed entry with the index SUPPLIED instead of rebuilt. resolve_type_reference_containment_binding takes inputs and builds a candidate index per call, which is correct for a single-reference API and is a cost-shape defect inside a walk: one index per reference over a whole transport is quadratic in the transport, and DESIGN section 6 fixes a proven cost shape regardless of the realized n. The category guard is the SAME contract -- a non-TypeOccurrence reference refuses with ReferenceBindingProjectionWrongCategory before any candidate is enumerated -- so this is the same decision on a shared index, not a second one." + +fn resolve_type_reference_via_structural_candidates( + transport: OccurrenceTransport, + index: OccurrenceCandidateIndex, + reference: ReferenceOccurrence +) -> ReferenceBindingProjection { + match reference.category { + TypeOccurrence => + resolve_reference_via_structural_candidates( + transport: transport, + index: index, + reference: reference, + ) + _ => + ReferenceBindingProjectionWrongCategory { + occurrence: reference.occurrence, + observed: reference.category, + } + } +} + +type JoinedDependencyBindingBuild { + projections_reversed: List +} + +data joined_dependency_binding_walk_note: String = "ONE POPULATION, TWO ADMISSION ROUTES. Clause-(e) references and type references reach the SAME BoundReferencePopulation through the same fold over the same candidate index: the projections are concatenated in transport order and bound_reference_population_from_projections is applied once. That is what makes this a join rather than two closures -- a second population would need its own totality law, its own refusal ordering and its own file projection, and the two could then disagree about one file. + +The index is built ONCE for the whole walk. Excluded categories contribute nothing and are not errors: a field or method reference is not a dependency-inducing position, which is a fact about the position and not a failure to bind." + +// THE SELECTED FORM IS THE GENERAL ONE, and the whole-transport form is the special case where every +// reference is selected. The distinction is load-bearing for a growing closure: the candidate index must +// be built over the WHOLE source-file universe (otherwise a bare reference to a not-yet-admitted +// provider is indistinguishable from a reference to nothing), while the references BOUND must come only +// from the files admitted so far (otherwise the closure binds the universe and every file is its own +// consumer). Two populations, one index -- so selection is a parameter here rather than a second walk. +fn joined_dependency_binding_walk_selected( + transport: OccurrenceTransport, + inputs: OccurrenceBindingCandidateInputs, + references: List +) -> StructuralBindingWalk { + match occurrence_candidate_index_build(transport: transport, inputs: inputs) { + OccurrenceCandidateIndexTransportRefused { refusal: refusal } => + StructuralBindingWalkRefused { + refusal: StructuralBindingTransportRefusal { refusal: refusal }, + } + OccurrenceCandidateIndexModulePathRefused { refusal: refusal } => + StructuralBindingWalkRefused { + refusal: StructuralBindingModulePathRefusal { refusal: refusal }, + } + OccurrenceCandidateIndexExposureRefused { refusal: refusal } => + StructuralBindingWalkRefused { + refusal: StructuralBindingExposureRefusal { refusal: refusal }, + } + OccurrenceCandidateIndexAuthoredOrderRefused { refusal: refusal } => + StructuralBindingWalkRefused { + refusal: StructuralBindingAuthoredOrderRefusal { refusal: refusal }, + } + OccurrenceCandidateIndexDeclarationBucketRefused { occurrence: occurrence } => + StructuralBindingWalkRefused { + refusal: StructuralBindingDeclarationBucketRefusal { occurrence: occurrence }, + } + OccurrenceCandidateIndexReady { index: index } => + StructuralBindingWalkReady { + population: bound_reference_population_from_projections( + projections: reverse(fold( + references, + init: JoinedDependencyBindingBuild { projections_reversed: [] }, + f: (acc, reference) => + match reference_dependency_admission_for_category(category: reference.category) { + ReferenceDependencyAdmittedClauseE => + JoinedDependencyBindingBuild { + projections_reversed: concat( + [resolve_reference_via_structural_candidates( + transport: transport, + index: index, + reference: reference, + )], + acc.projections_reversed, + ), + } + ReferenceDependencyAdmittedTypeReference => + JoinedDependencyBindingBuild { + projections_reversed: concat( + [resolve_type_reference_via_structural_candidates( + transport: transport, + index: index, + reference: reference, + )], + acc.projections_reversed, + ), + } + ReferenceDependencyExcluded { category: _ } => + match reference_base_child(references: references, reference: reference) { + Absent => acc + Present { value: base } => + match module_declaration_named_by_reference(index: index, base: base) { + Absent => acc + Present { value: provider } => + JoinedDependencyBindingBuild { + projections_reversed: concat( + [resolve_module_member_projection( + transport: transport, + index: index, + reference: reference, + provider: provider, + )], + acc.projections_reversed, + ), + } + } + } + }, + ).projections_reversed), + ), + } + } +} + +fn joined_dependency_binding_walk( + transport: OccurrenceTransport, + inputs: OccurrenceBindingCandidateInputs +) -> StructuralBindingWalk { + joined_dependency_binding_walk_selected( + transport: transport, + inputs: inputs, + references: transport.references, + ) +} + data reference_derived_dependency_binding_reference_filter_note: String = "Clause-(e) structural walks bind only reference categories that occurrence_category_clause_e_dependency_inducing_verdict classifies as OccurrenceCategoryClauseEDependencyInducing. TypeOccurrence references are N2's resolve_type_reference_containment_binding lane — parser-produced return-type annotations must not refuse the whole population when Int is not module-declared. Parser witnesses that need per-name resolution already avoid whole-transport walks for this reason; production assembled-closure projection applies the same boundary here." fn reference_derived_dependency_binding_references( @@ -2295,10 +3106,17 @@ fn reference_derived_clause_e_production_from_assembled_closure( } } +data reference_binding_ambiguous_candidates_note: String = "AMBIGUITY CARRIES ITS CANDIDATES, and the reason it must is that the information was already computed and then discarded here. resolve_reference_occurrence_binding returns OccurrenceAmbiguous { occurrence, candidates }, and this projection used to match `candidates: _` -- so a caller learned THAT a reference was ambiguous and never WHICH declarations competed, which is the fact an operator needs to discharge it and the fact a pool-independence claim is actually about. A count is not an identity: two ambiguity outcomes with the same cardinality over different declarations are different states with different remedies. + +The carrier is the upstream AmbiguousBindingCandidates itself rather than a local list, for two reasons. It is the single authority for this shape, so re-spelling it here would be the nicknaming DESIGN section 3 forbids; and it is head-plus-second-plus-tail, so a one-candidate `ambiguity` has no constructor -- at-least-two-ness is structural rather than checked, and an arm that fabricated a single-candidate ambiguity cannot be written." + type ReferenceBindingProjection = ReferenceBindingProjectionBound { provider: BoundReferenceProvider } | ReferenceBindingProjectionUnbound { occurrence: OccurrenceId } - | ReferenceBindingProjectionAmbiguous { occurrence: OccurrenceId } + | ReferenceBindingProjectionAmbiguous { + occurrence: OccurrenceId, + candidates: AmbiguousBindingCandidates, + } | ReferenceBindingProjectionTransportRefused { refusal: OccurrenceTransportRefusal } | ReferenceBindingProjectionModulePathRefused { refusal: OccurrenceModulePathIndexRefusal } | ReferenceBindingProjectionExposureRefused { refusal: DeclarationExposureIndexRefusal } @@ -2310,6 +3128,32 @@ type ReferenceBindingProjection observed: OccurrenceCategory, } +// THE SUBJECT OF A PROJECTION, so a refusal can be located instead of naming an allocator ordinal. +// Total over the closed coproduct, and Optional rather than fabricating an id: the four index-build +// refusal arms are failures of the whole transport, not of one occurrence, and answering them with any +// particular occurrence would point a reader at an innocent site. +fn reference_binding_projection_occurrence( + projection: ReferenceBindingProjection +) -> OccurrenceId? { + match projection { + ReferenceBindingProjectionBound { provider: provider } => + Present { value: provider.reference_occurrence } + ReferenceBindingProjectionUnbound { occurrence: occurrence } => Present { value: occurrence } + ReferenceBindingProjectionAmbiguous { occurrence: occurrence, candidates: _ } => + Present { value: occurrence } + ReferenceBindingProjectionDeclarationBucketRefused { occurrence: occurrence } => + Present { value: occurrence } + ReferenceBindingProjectionModulePathMissing { occurrence: occurrence } => + Present { value: occurrence } + ReferenceBindingProjectionWrongCategory { occurrence: occurrence, observed: _ } => + Present { value: occurrence } + ReferenceBindingProjectionTransportRefused { refusal: _ } => none + ReferenceBindingProjectionModulePathRefused { refusal: _ } => none + ReferenceBindingProjectionExposureRefused { refusal: _ } => none + ReferenceBindingProjectionAuthoredOrderRefused { refusal: _ } => none + } +} + data resolve_type_reference_containment_binding_note: String = "N2 typed entry (gunbc.type_reference_containment_binding slice 1): refuses non-TypeOccurrence references at the API boundary before candidate enumeration — category is part of the binding contract, not an after-the-fact filter. TypeOccurrence references delegate to resolve_reference_binding_via_structural_candidates unchanged." fn resolve_type_reference_containment_binding( @@ -2334,15 +3178,51 @@ fn resolve_type_reference_containment_binding( data resolve_reference_via_structural_candidates_note: String = "THE P2a entry point. It builds the exact candidate population for one reference from the precomputed index (candidate_occurrence_ids_for_reference) and hands that population to std.occurrence_binding_resolve.resolve_reference_occurrence_binding as supplied_candidates -- P1's fold over occurrence_binding_from_candidates is the only cardinality decision in this path. Only the OccurrenceBound arm is projected into a BoundReferenceProvider; Unbound and Ambiguous are surfaced as their own typed, located outcomes so a caller can never mistake 'no edge' for 'one edge'." +fn resolve_module_member_projection( + transport: OccurrenceTransport, + index: OccurrenceCandidateIndex, + reference: ReferenceOccurrence, + provider: DeclarationOccurrence +) -> ReferenceBindingProjection { + let segment_name = + match map_get(index.entries_by_id, reference.occurrence.value) { + Absent => "" + Present { value: entry } => entry.projection.authored_name + } + resolve_reference_from_candidates( + transport: transport, + index: index, + reference: reference, + candidates: module_member_candidate_ids( + index: index, + provider: provider, + segment_name: segment_name, + ), + ) +} + fn resolve_reference_via_structural_candidates( transport: OccurrenceTransport, index: OccurrenceCandidateIndex, reference: ReferenceOccurrence ) -> ReferenceBindingProjection { - let candidates = candidate_occurrence_ids_for_reference( + resolve_reference_from_candidates( + transport: transport, index: index, reference: reference, + candidates: candidate_occurrence_ids_for_reference( + index: index, + reference: reference, + ), ) +} + +fn resolve_reference_from_candidates( + transport: OccurrenceTransport, + index: OccurrenceCandidateIndex, + reference: ReferenceOccurrence, + candidates: List +) -> ReferenceBindingProjection { match resolve_reference_occurrence_binding( transport: transport, occurrence: reference.occurrence, @@ -2354,8 +3234,11 @@ fn resolve_reference_via_structural_candidates( match result { OccurrenceUnbound { occurrence: _ } => ReferenceBindingProjectionUnbound { occurrence: reference.occurrence } - OccurrenceAmbiguous { occurrence: _, candidates: _ } => - ReferenceBindingProjectionAmbiguous { occurrence: reference.occurrence } + OccurrenceAmbiguous { occurrence: _, candidates: candidates } => + ReferenceBindingProjectionAmbiguous { + occurrence: reference.occurrence, + candidates: candidates, + } OccurrenceBound { binding: binding } => let declaration_occurrence = binding.candidate.containment.terminal match module_of_occurrence( diff --git a/dag/std/occurrence_binding_resolve.dag b/dag/std/occurrence_binding_resolve.dag index 9ae1a532f12..dfc4aef174e 100644 --- a/dag/std/occurrence_binding_resolve.dag +++ b/dag/std/occurrence_binding_resolve.dag @@ -29,6 +29,7 @@ import std.occurrence_identity { NamespaceSegmentOccurrence, FieldOccurrence, MethodOccurrence, + ModuleValueOccurrence, DeclarationRole, ReferenceRole, DuplicateSuppliedCandidateIdentity, @@ -69,6 +70,8 @@ fn occurrence_category_binding_verdict( LexicalValueOccurrence => OccurrenceCategoryBindingAdmissible CallableOccurrence => OccurrenceCategoryBindingAdmissible ConstructorOccurrence => OccurrenceCategoryBindingAdmissible + ModuleValueOccurrence => OccurrenceCategoryBindingAdmissible + NamespaceSegmentOccurrence => OccurrenceCategoryBindingAdmissible _ => OccurrenceCategoryBindingInadmissible { reference: reference, declaration: declaration, @@ -93,6 +96,10 @@ fn occurrence_category_binding_verdict( FieldOccurrence => match declaration { FieldOccurrence => OccurrenceCategoryBindingAdmissible + ModuleValueOccurrence => OccurrenceCategoryBindingAdmissible + CallableOccurrence => OccurrenceCategoryBindingAdmissible + TypeOccurrence => OccurrenceCategoryBindingAdmissible + ConstructorOccurrence => OccurrenceCategoryBindingAdmissible _ => OccurrenceCategoryBindingInadmissible { reference: reference, declaration: declaration, @@ -106,6 +113,14 @@ fn occurrence_category_binding_verdict( declaration: declaration, } } + ModuleValueOccurrence => + match declaration { + ModuleValueOccurrence => OccurrenceCategoryBindingAdmissible + _ => OccurrenceCategoryBindingInadmissible { + reference: reference, + declaration: declaration, + } + } NamespaceSegmentOccurrence => match declaration { NamespaceSegmentOccurrence => OccurrenceCategoryBindingAdmissible diff --git a/dag/std/occurrence_identity.dag b/dag/std/occurrence_identity.dag index c7fe28db671..c239fc209ed 100644 --- a/dag/std/occurrence_identity.dag +++ b/dag/std/occurrence_identity.dag @@ -58,6 +58,7 @@ type OccurrenceCategory | NamespaceSegmentOccurrence | FieldOccurrence | MethodOccurrence + | ModuleValueOccurrence type OccurrenceCategoryModuleScopeExposureVerdict = OccurrenceCategoryModuleScopeExposed @@ -65,7 +66,7 @@ type OccurrenceCategoryModuleScopeExposureVerdict category: OccurrenceCategory, } -data occurrence_category_module_scope_exposure_verdict_note: String = "Canonical module-scope exposure surface for declaration categories (review 45716 predicate-dissolution): consumers match OccurrenceCategoryModuleScopeExposureVerdict directly — not a parallel Bool predicate over the OccurrenceCategory coproduct. CallableOccurrence, TypeOccurrence, ConstructorOccurrence, and NamespaceSegmentOccurrence are module-scope members; LexicalValueOccurrence, FieldOccurrence, and MethodOccurrence are not." +data occurrence_category_module_scope_exposure_verdict_note: String = "Canonical module-scope exposure surface for declaration categories (review 45716 predicate-dissolution): consumers match OccurrenceCategoryModuleScopeExposureVerdict directly — not a parallel Bool predicate over the OccurrenceCategory coproduct. CallableOccurrence, TypeOccurrence, ConstructorOccurrence, NamespaceSegmentOccurrence and ModuleValueOccurrence are module-scope members; LexicalValueOccurrence, FieldOccurrence, and MethodOccurrence are not. ModuleValueOccurrence exists because a module-scope `data name: T = v` is a module MEMBER and LexicalValueOccurrence is explicitly not one: reusing the lexical arm would have declared every module datum unexported, and reusing CallableOccurrence would have nicknamed a value as callable. Before it existed the item was classified Unclassified and bound no name at all, so a cross-file reference to any module datum could not bind and every module-value edge was missing from the derived closure." fn occurrence_category_module_scope_exposure_verdict( category: OccurrenceCategory @@ -81,6 +82,7 @@ fn occurrence_category_module_scope_exposure_verdict( OccurrenceCategoryModuleScopeNotExposed { category: category } MethodOccurrence => OccurrenceCategoryModuleScopeNotExposed { category: category } + ModuleValueOccurrence => OccurrenceCategoryModuleScopeExposed } } @@ -90,7 +92,7 @@ type OccurrenceCategoryClauseEDependencyInducingVerdict category: OccurrenceCategory, } -data occurrence_category_clause_e_dependency_inducing_verdict_note: String = "Canonical clause-(e) reference-category filter surface (review 50193 predicate-dissolution): consumers match OccurrenceCategoryClauseEDependencyInducingVerdict directly — not a parallel inline match over the OccurrenceCategory coproduct. LexicalValueOccurrence, CallableOccurrence, ConstructorOccurrence, and NamespaceSegmentOccurrence references may induce cross-file file dependencies; TypeOccurrence is N2's resolve_type_reference_containment_binding lane; FieldOccurrence and MethodOccurrence do not participate in clause-(e) structural walks." +data occurrence_category_clause_e_dependency_inducing_verdict_note: String = "Canonical clause-(e) reference-category filter surface (review 50193 predicate-dissolution): consumers match OccurrenceCategoryClauseEDependencyInducingVerdict directly — not a parallel inline match over the OccurrenceCategory coproduct. LexicalValueOccurrence, CallableOccurrence, ConstructorOccurrence, NamespaceSegmentOccurrence and ModuleValueOccurrence references may induce cross-file file dependencies; TypeOccurrence is N2's resolve_type_reference_containment_binding lane; FieldOccurrence and MethodOccurrence do not participate in clause-(e) structural walks." fn occurrence_category_clause_e_dependency_inducing_verdict( category: OccurrenceCategory @@ -106,6 +108,7 @@ fn occurrence_category_clause_e_dependency_inducing_verdict( OccurrenceCategoryClauseEDependencyNotInducing { category: category } MethodOccurrence => OccurrenceCategoryClauseEDependencyNotInducing { category: category } + ModuleValueOccurrence => OccurrenceCategoryClauseEDependencyInducing } } @@ -148,6 +151,21 @@ type OccurrenceTransport { references: List } +data reference_path_note: String = "ONE CARRIER FOR BOTH SPELLINGS OF A PROJECTION. The dot is containment projection, and the two syntactic positions that write one spell it differently: a TYPE path arrives as a single occurrence whose authored_name is the whole dotted text (live_tree.LiveTreeDisposition), while an EXPRESSION path arrives as a base reference plus one FieldOccurrence node per segment, the base reachable as the segment's containment child. ReferencePath is what both fold into, so the resolution rule is authored once and neither position carries a private one. The head is a NAME plus the span that located it, not an occurrence: the type spelling has no separate occurrence for its head, and pretending it does would mint an identity the parser never allocated. A segment likewise carries its own span where one exists and the whole path's span where the spelling gives it none -- an honestly coarse location, never a fabricated offset into a blob. THE LAW THE SHAPE ENFORCES: a path with no segments is an ordinary bare reference, and every segment is a step that must land on exactly one containment child of the previous declaration -- so no terminal declaration, no binding, and no dependency-ready outcome from a head that merely bound. What this replaces is two admissions that stood in for it: the value-reference arm admitting a namespace category (the head widen) and the field-reference arm admitting module-member categories (the segment widen); both were the reference site guessing at a question the path shape decides." + +type ReferencePathSegment { + name: String + diagnostic_span: SourceSpan + occurrence: OccurrenceId? +} + +type ReferencePath { + head_name: String + head_span: SourceSpan + head_occurrence: OccurrenceId + segments: List +} + type OccurrenceIdentityAcceptanceLaw { id: String required_receipt: String diff --git a/dag/std/reference_derived_closure.dag b/dag/std/reference_derived_closure.dag new file mode 100644 index 00000000000..cc45b46b07f --- /dev/null +++ b/dag/std/reference_derived_closure.dag @@ -0,0 +1,539 @@ +module std.reference_derived_closure + +import std.types { Bool, String, List, Int, NonEmptyStr, FilePath } +import std.occurrence_identity { + OccurrenceTransport, + ReferenceOccurrence, + OccurrenceId, + OccurrenceIndexEntry, + OccurrenceProjection, +} +import std.occurrence_binding_candidates { + OccurrenceBindingCandidateInputs, + OccurrenceModulePathIndexRefusal, + module_by_occurrence_build, + module_of_occurrence, + CrossFileBindingClosureRow, + AssembledCrossFileBindingClosureReady, + AssembledCrossFileBindingClosureRefused, + AssembledCrossFileBindingClosureRefusal, + assemble_cross_file_binding_closure, + BoundReferencePopulation, + AllReferencesBound, + ReferencePopulationRefused, + reference_binding_projection_occurrence, + CrossFileBindingProvenanceProjection, + StructuralBindingWalkReady, + StructuralBindingWalkRefused, + StructuralBindingIndexRefusal, + joined_dependency_binding_walk_selected, + ReferenceDerivedDependencyProjectionReady, + ReferenceDerivedDependencyProjectionBindingRefused, + ReferenceDerivedDependencyProjectionFileRefused, + reference_derived_dependency_projection, + ModulePathFileIndexReady, + ModulePathFileIndexRefused, + ModulePathFileIndexRefusal, + module_path_file_index_from_rows, + file_for_module_path, + DirectFileDependency, + CrossFileBindingProvenance, +} + +// TWO POPULATIONS, DELIBERATELY NOT ONE. `universe` is every ordinary source file the compile is over; +// `admitted` is the per-entry closure discovered so far. The candidate index is built over the universe +// and the references bound are selected from the admitted files only. +// +// Collapsing them deadlocks, and the deadlock is not hypothetical: with the index built over admitted +// files alone, a bare reference to a provider that has not been admitted has no declaration to bind to, +// so it is Unbound, so no provider file is derived, so nothing is ever admitted. Discovery has to see the +// declaration before the file carrying it is part of the closure -- a module-to-file index cannot supply +// that, because storage realization says where a module lives and never which module declares a name. +type ReferenceDerivedClosureState { + admitted_files: List + universe: List +} + +// One ordinary source file's parse, supplied by whoever owns parsing (the authored-source parser is in +// the v1 seed). A provider file derived from a binding but absent from the universe REFUSES: an omitted +// file is a wrong closure, not a smaller one. +type ParsedFileSupply { + file: FilePath + row: CrossFileBindingClosureRow +} + +type ReferenceDerivedClosureRefusal + = ClosureProviderFileOutsideUniverse { file: FilePath } + | ClosureNoEntryAdmitted + | ClosureAssemblyRefused { refusal: AssembledCrossFileBindingClosureRefusal } + | ClosureWalkRefused { refusal: StructuralBindingIndexRefusal } + | ClosureBindingRefused { + population: BoundReferencePopulation, + first_failure_locus: OccurrenceProjection, + failure_count: Int, + } + | ClosureRefusalNotLocatable { occurrence: OccurrenceId } + | ClosurePopulationFailureHasNoOccurrence { population: BoundReferencePopulation } + | ClosureFileProjectionRefused { + first_failure: CrossFileBindingProvenanceProjection, + more_failures: List, + } + | ClosureModuleStorageRefused { refusal: ModulePathFileIndexRefusal } + | ClosureOccurrenceModulePathRefused { refusal: OccurrenceModulePathIndexRefusal } + | ClosureReferenceFileUnresolved { occurrence: OccurrenceId } + +// THREE OUTCOMES, and the absence of a fourth is the point. No arm returns the edges for whichever +// references happened to bind: an unbound or ambiguous reference in the selected population refuses the +// whole step, because a closure short by one provider reads downstream as complete. +type ReferenceDerivedClosureStep + = ClosureStepAdvanced { + state: ReferenceDerivedClosureState, + newly_pending: List, + edges: List, + provenance: List, + } + | ClosureStepSettled { + state: ReferenceDerivedClosureState, + edges: List, + provenance: List, + } + | ClosureStepRefused { refusal: ReferenceDerivedClosureRefusal } + +fn no_file_paths() -> List { [] } + +fn no_selected_references() -> List { [] } + +fn file_path_in(files: List, file: FilePath) -> Bool { + fold(files, init: false, f: (found, candidate) => + found || ((candidate as String) == (file as String))) +} + +fn universe_row_for_file( + universe: List, + file: FilePath +) -> ParsedFileSupply? { + fold(universe, init: none, f: (found, row) => + match found { + Present { value: _ } => found + Absent => + match (row.file as String) == (file as String) { + true => Present { value: row } + false => Absent + } + }) +} + +type UniversePartition { + consumer: CrossFileBindingClosureRow? + rest: List +} + +// The entry is the assembly's consumer and every other universe file is a provider ROW -- which is only +// a statement about rekeying and exposure, not about the closure: an assembled provider row contributes +// candidates, and whether its file is in the closure is decided by the edges, below. +fn partition_universe( + universe: List, + entry: FilePath +) -> UniversePartition { + fold( + universe, + init: UniversePartition { consumer: none, rest: [] }, + f: (acc, supply) => + match (supply.file as String) == (entry as String) { + true => UniversePartition { consumer: Present { value: supply.row }, rest: acc.rest } + false => + UniversePartition { + consumer: acc.consumer, + rest: concat(acc.rest, [supply.row]), + } + }, + ) +} + +type SelectedReferenceBuild { + references: List + unresolved: OccurrenceId? +} + +// Selection is by the reference's OWN file, derived from its module path and the module-to-file index -- +// not by which row it arrived in, because assembly rekeys every occurrence into one transport and the row +// boundary is gone by then. A reference whose file cannot be derived REFUSES rather than being dropped: +// dropped, it would silently narrow the selected population and the step would report fewer edges as if +// the consumer had fewer dependencies. +fn selected_references_for_admitted_files( + transport: OccurrenceTransport, + module_by_occurrence: Map, + module_files: Map, + admitted: List +) -> SelectedReferenceBuild { + fold( + transport.references, + init: SelectedReferenceBuild { references: no_selected_references(), unresolved: none }, + f: (acc, reference) => + match acc.unresolved { + Present { value: _ } => acc + Absent => + match module_of_occurrence( + module_by_occurrence: module_by_occurrence, + occurrence: reference.occurrence, + ) { + Absent => + SelectedReferenceBuild { + references: acc.references, + unresolved: Present { value: reference.occurrence }, + } + Present { value: module_path } => + match file_for_module_path(module_files: module_files, module_path: module_path) { + Absent => + SelectedReferenceBuild { + references: acc.references, + unresolved: Present { value: reference.occurrence }, + } + Present { value: file } => + match file_path_in(files: admitted, file: file) { + false => acc + true => + SelectedReferenceBuild { + references: concat(acc.references, [reference]), + unresolved: none, + } + } + } + } + }, + ) +} + + +// A REFUSAL NAMING AN OccurrenceId IS NOT A LOCATED REFUSAL. The id is an allocator ordinal — it means +// nothing to a reader and nothing to a tool, and rekeying makes it unstable across universes, so a +// consumer cannot even join it back to the file it came from. The authored name and byte span are in the +// assembled transport's own index, one lookup away, so carrying them is not new observation: it is +// declining to discard what the step already holds. +// +// An occurrence the index cannot resolve is its OWN refusal rather than an absent locus, because a +// population refusal whose subject cannot be located is a different failure from an ordinary unbound +// reference and has a different cause (the transport and its index disagree). +fn locate_occurrence( + transport: OccurrenceTransport, + occurrence: OccurrenceId +) -> OccurrenceProjection? { + fold(transport.index.entries, init: none, f: (found, entry) => + match found { + Present { value: _ } => found + Absent => + match entry.projection.occurrence.value == occurrence.value { + true => Present { value: entry.projection } + false => Absent + } + }) +} + +fn population_first_failure_occurrence( + population: BoundReferencePopulation +) -> OccurrenceId? { + match population { + AllReferencesBound { providers: _ } => none + ReferencePopulationRefused { first_failure: failure, more_failures: _ } => + reference_binding_projection_occurrence(projection: failure) + } +} + +fn population_failure_count(population: BoundReferencePopulation) -> Int { + match population { + AllReferencesBound { providers: _ } => 0 + ReferencePopulationRefused { first_failure: _, more_failures: more } => 1 + length(more) + } +} + +fn closure_binding_refusal( + transport: OccurrenceTransport, + population: BoundReferencePopulation +) -> ReferenceDerivedClosureRefusal { + match population_first_failure_occurrence(population: population) { + Absent => ClosurePopulationFailureHasNoOccurrence { population: population } + Present { value: occurrence } => + match locate_occurrence(transport: transport, occurrence: occurrence) { + Absent => ClosureRefusalNotLocatable { occurrence: occurrence } + Present { value: locus } => + ClosureBindingRefused { + population: population, + first_failure_locus: locus, + failure_count: population_failure_count(population: population), + } + } + } +} + +fn provider_files_of(edges: List) -> List { + fold(edges, init: no_file_paths(), f: (acc, edge) => + match file_path_in(files: acc, file: edge.provider_file) { + true => acc + false => concat(acc, [edge.provider_file]) + }) +} + +fn unseen_provider_files( + providers: List, + admitted: List +) -> List { + fold(providers, init: no_file_paths(), f: (acc, provider) => + match file_path_in(files: admitted, file: provider) || file_path_in(files: acc, file: provider) { + true => acc + false => concat(acc, [provider]) + }) +} + +fn first_file_outside_universe( + files: List, + universe: List +) -> FilePath? { + fold(files, init: none, f: (found, file) => + match found { + Present { value: _ } => found + Absent => + match universe_row_for_file(universe: universe, file: file) { + Present { value: _ } => none + Absent => Present { value: file } + } + }) +} + +// ONE ITERATION of the fixed point. Assemble the WHOLE universe into one collision-free transport, bind +// every dependency-inducing reference that belongs to an ADMITTED file through the joined walk, project +// each accepted binding to its provider file, and make the unseen providers pending. Termination: the +// universe is finite and `admitted_files` grows strictly on every Advanced step, so `Settled` is reached +// in at most |universe| steps. +fn reference_derived_closure_step( + state: ReferenceDerivedClosureState +) -> ReferenceDerivedClosureStep { + match first(state.admitted_files) { + Absent => ClosureStepRefused { refusal: ClosureNoEntryAdmitted } + Present { value: entry } => + let partition = partition_universe(universe: state.universe, entry: entry) + match partition.consumer { + Absent => ClosureStepRefused { refusal: ClosureNoEntryAdmitted } + Present { value: consumer_row } => + match assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: partition.rest, + ) { + AssembledCrossFileBindingClosureRefused { refusal: refusal } => + ClosureStepRefused { refusal: ClosureAssemblyRefused { refusal: refusal } } + AssembledCrossFileBindingClosureReady { + transport: transport, + inputs: inputs, + module_files: module_file_rows, + consumer_file: _, + } => + match module_path_file_index_from_rows(rows: module_file_rows) { + ModulePathFileIndexRefused { refusal: refusal } => + ClosureStepRefused { + refusal: ClosureModuleStorageRefused { refusal: refusal }, + } + ModulePathFileIndexReady { entries: module_files } => + let module_index = module_by_occurrence_build(rows: inputs.module_paths) + match module_index.refusal { + Present { value: refusal } => + ClosureStepRefused { + refusal: ClosureOccurrenceModulePathRefused { refusal: refusal }, + } + Absent => + let selected = selected_references_for_admitted_files( + transport: transport, + module_by_occurrence: module_index.module_by_occurrence, + module_files: module_files, + admitted: state.admitted_files, + ) + match selected.unresolved { + Present { value: occurrence } => + ClosureStepRefused { + refusal: ClosureReferenceFileUnresolved { occurrence: occurrence }, + } + Absent => + match joined_dependency_binding_walk_selected( + transport: transport, + inputs: inputs, + references: selected.references, + ) { + StructuralBindingWalkRefused { refusal: refusal } => + ClosureStepRefused { refusal: ClosureWalkRefused { refusal: refusal } } + StructuralBindingWalkReady { population: population } => + match reference_derived_dependency_projection( + population: population, + module_files: module_files, + ) { + ReferenceDerivedDependencyProjectionBindingRefused { + population: refused, + } => + ClosureStepRefused { + refusal: closure_binding_refusal( + transport: transport, + population: refused, + ), + } + ReferenceDerivedDependencyProjectionFileRefused { + first_failure: first_failure, + more_failures: more_failures, + } => + ClosureStepRefused { + refusal: ClosureFileProjectionRefused { + first_failure: first_failure, + more_failures: more_failures, + }, + } + ReferenceDerivedDependencyProjectionReady { + binding_provenance: provenance, + file_dependencies: edges, + bound_population: _, + } => + let newly_pending = unseen_provider_files( + providers: provider_files_of(edges: edges), + admitted: state.admitted_files, + ) + match first_file_outside_universe( + files: newly_pending, + universe: state.universe, + ) { + Present { value: missing } => + ClosureStepRefused { + refusal: ClosureProviderFileOutsideUniverse { + file: missing, + }, + } + Absent => + let next = ReferenceDerivedClosureState { + admitted_files: concat( + state.admitted_files, + newly_pending, + ), + universe: state.universe, + } + match length(newly_pending) == 0 { + true => + ClosureStepSettled { + state: next, + edges: edges, + provenance: provenance, + } + false => + ClosureStepAdvanced { + state: next, + newly_pending: newly_pending, + edges: edges, + provenance: provenance, + } + } + } + } + } + } + } + } + } + } + } +} + +// The entry is admitted at once, because the entry IS the compile's subject; the universe is the ordinary +// source-file population and carries the entry's own parse like every other file's. +fn reference_derived_closure_initial( + entry: FilePath, + universe: List +) -> ReferenceDerivedClosureState { + ReferenceDerivedClosureState { + admitted_files: [entry], + universe: universe, + } +} + +// NO PROJECTION ANSWERS A REFUSED STEP. `closure_step_edges` used to answer a refusal with `[]` and +// `closure_step_state` with `Absent` -- the absorbing fallback this lane keeps removing, exported and +// waiting for a consumer: a caller reading `[]` cannot tell "no dependencies" from "the step refused". +// Both are deleted; a consumer matches the three arms, which is the only reading that cannot lie. +fn closure_step_is_settled(step: ReferenceDerivedClosureStep) -> Bool { + match step { + ClosureStepSettled { state: _, edges: _, provenance: _ } => true + ClosureStepAdvanced { state: _, newly_pending: _, edges: _, provenance: _ } => false + ClosureStepRefused { refusal: _ } => false + } +} + +// The fixed point itself: iterate until settled or refused. Bounded by the universe size, so the bound is +// a fact about the input rather than a tuning constant. +type ReferenceDerivedClosureFixedPoint + = ClosureFixedPointSettled { + admitted_files: List, + edges: List, + provenance: List, + iterations: Int, + } + | ClosureFixedPointRefused { refusal: ReferenceDerivedClosureRefusal, iterations: Int } + | ClosureFixedPointExhausted { admitted_files: List, iterations: Int } + +type ClosureFixedPointFold { + state: ReferenceDerivedClosureState + step: ReferenceDerivedClosureStep? + iterations: Int +} + +fn reference_derived_closure_fixed_point( + entry: FilePath, + universe: List +) -> ReferenceDerivedClosureFixedPoint { + let initial = reference_derived_closure_initial(entry: entry, universe: universe) + let run = fold( + universe, + init: ClosureFixedPointFold { + state: initial, + step: Present { value: reference_derived_closure_step(state: initial) }, + iterations: 1, + }, + f: (acc, _ignored) => + match acc.step { + Present { value: settled_or_refused } => + match closure_step_is_settled(step: settled_or_refused) { + true => acc + false => + match settled_or_refused { + ClosureStepRefused { refusal: _ } => acc + ClosureStepSettled { state: _, edges: _, provenance: _ } => acc + ClosureStepAdvanced { state: advanced, newly_pending: _, edges: _, provenance: _ } => + let next = reference_derived_closure_step(state: advanced) + ClosureFixedPointFold { + state: advanced, + step: Present { value: next }, + iterations: acc.iterations + 1, + } + } + } + Absent => + let next = reference_derived_closure_step(state: acc.state) + ClosureFixedPointFold { + state: acc.state, + step: Present { value: next }, + iterations: acc.iterations + 1, + } + }, + ) + match run.step { + Absent => ClosureFixedPointExhausted { admitted_files: [], iterations: run.iterations } + Present { value: final_step } => + match final_step { + ClosureStepSettled { state: state, edges: edges, provenance: provenance } => + ClosureFixedPointSettled { + admitted_files: state.admitted_files, + edges: edges, + provenance: provenance, + iterations: run.iterations, + } + ClosureStepRefused { refusal: refusal } => + ClosureFixedPointRefused { refusal: refusal, iterations: run.iterations } + ClosureStepAdvanced { state: state, newly_pending: _, edges: _, provenance: _ } => + ClosureFixedPointExhausted { + admitted_files: state.admitted_files, + iterations: run.iterations, + } + } + } +} diff --git a/dag/test/claim/cross_file_binding_assembly_witness_test.dag b/dag/test/claim/cross_file_binding_assembly_witness_test.dag index 886625980e7..f7becf942a2 100644 --- a/dag/test/claim/cross_file_binding_assembly_witness_test.dag +++ b/dag/test/claim/cross_file_binding_assembly_witness_test.dag @@ -29,7 +29,7 @@ import std.occurrence_binding_candidates { DeclarationExposureGrounding, NamespaceStructuralRootExposure, ModuleLocalMemberExposure, - CrossFileProviderExportedExposure, + ModuleLocalMemberExposure, declaration_exposure_from_containment, ModulePathFileRow, ModulePathFileIndex, @@ -94,6 +94,13 @@ fn cfba_reference(occurrence: OccurrenceId, containment: OccurrenceContainmentPa // --- DeclarationExposureGrounding: all three variants pairwise-discriminated --- +// The two rows that stood here -- cross_file_provider_exported_exposure_at_module_root_upgrades_to_root +// and ..._at_direct_child_upgrades_to_root -- asserted that a provider file's module member became +// RootExposure. That arm is deleted (see declaration_exposure_from_containment_note), so the rows had +// no subject: they called the surviving grounding and demanded the deleted answer. They are not kept +// as regression evidence, because what would be regressing is a behaviour the model no longer has; +// the two module_local_member_exposure rows beside this one assert the surviving answer at both +// containment shapes, which is the same coverage read forward. test fn module_local_member_exposure_at_module_root_holds() -> Bool { let decl = OccurrenceId { value: 10 } let path = cfba_containment(ancestors: [], terminal: decl) @@ -101,25 +108,13 @@ test fn module_local_member_exposure_at_module_root_holds() -> Bool { module_path: cfba_path(dotted: "extdeps.a"), containment: path, grounding: ModuleLocalMemberExposure, + category: TypeOccurrence, ) { ModuleExposure { module: module } => module == cfba_path(dotted: "extdeps.a") _ => false } } -test fn cross_file_provider_exported_exposure_at_module_root_upgrades_to_root_holds() -> Bool { - let decl = OccurrenceId { value: 11 } - let path = cfba_containment(ancestors: [], terminal: decl) - match declaration_exposure_from_containment( - module_path: cfba_path(dotted: "extdeps.a"), - containment: path, - grounding: CrossFileProviderExportedExposure, - ) { - RootExposure => true - _ => false - } -} - test fn module_local_member_exposure_at_direct_child_stays_module_exposure_holds() -> Bool { let outer = OccurrenceId { value: 1 } let decl = OccurrenceId { value: 20 } @@ -128,26 +123,13 @@ test fn module_local_member_exposure_at_direct_child_stays_module_exposure_holds module_path: cfba_path(dotted: "extdeps.b"), containment: path, grounding: ModuleLocalMemberExposure, + category: TypeOccurrence, ) { ModuleExposure { module: module } => module == cfba_path(dotted: "extdeps.b") _ => false } } -test fn cross_file_provider_exported_exposure_at_direct_child_upgrades_to_root_holds() -> Bool { - let outer = OccurrenceId { value: 1 } - let decl = OccurrenceId { value: 21 } - let path = cfba_containment(ancestors: [outer], terminal: decl) - match declaration_exposure_from_containment( - module_path: cfba_path(dotted: "extdeps.b"), - containment: path, - grounding: CrossFileProviderExportedExposure, - ) { - RootExposure => true - _ => false - } -} - test fn namespace_structural_root_exposure_at_direct_child_stays_module_exposure_holds() -> Bool { let outer = OccurrenceId { value: 1 } let decl = OccurrenceId { value: 22 } @@ -156,6 +138,7 @@ test fn namespace_structural_root_exposure_at_direct_child_stays_module_exposure module_path: cfba_path(dotted: "extdeps.c"), containment: path, grounding: NamespaceStructuralRootExposure, + category: TypeOccurrence, ) { ModuleExposure { module: module } => module == cfba_path(dotted: "extdeps.c") _ => false diff --git a/dag/test/claim/module_storage_row_projection_witness_test.dag b/dag/test/claim/module_storage_row_projection_witness_test.dag new file mode 100644 index 00000000000..981f6a65458 --- /dev/null +++ b/dag/test/claim/module_storage_row_projection_witness_test.dag @@ -0,0 +1,181 @@ +module test.claim.module_storage_row_projection_witness_test + +import std.types { Bool, String, List, NonEmptyStr, FilePath } +import std.algebra { Empty, FreeMonoid } +import std.occurrence_binding_candidates { ModulePathFileRow } +import v2.std.artifact { Artifact, TestClaimArtifact, SourceFile } +import v2.std.provenance { span_index_empty } +import v2.std.cross_tree.import_model { DagTree, V2Tree } +import v2.compiler.source_authority { + ModuleStorageBinding, + ModuleStorageIndex, + ParsedFromSource, + ProducedByBehavior, + ModulePathFileRowProjection, + ModulePathFileRowProjected, + ModulePathFileRowNoStorageRealization, + ModulePathFileRowsFromIndex, + ModulePathFileRowsFromIndexReady, + ModulePathFileRowsFromIndexRefused, + module_path_file_row_from_storage_binding, + module_path_file_rows_from_storage_index, +} + +data msrp_note: String = "EXECUTING CONSUMER for the ModulePathFileRow projection off ModuleStorageBinding. + +module_path_file_row_note has declared ModuleStorageBinding the single authority for the path-to-module binding, and ModulePathFileRow a scaffold projection OF it, since it was written. Nothing performed that projection: every ModulePathFileRow in the tree was a hand-authored literal inside a witness, so the two representations coexisted with no derivation between them -- the parallel-representation state the note said it was avoiding. These rows exercise the derivation. + +The load-bearing row is the WHOLE refusal. A produced-by-behavior module has no file, and the tempting arm is to project the file-backed bindings and drop it: every positive row here would still pass, and a consumer would receive a row set it believed was the whole storage picture with a module silently missing. That is the empty-observation narrow at set grain. So one unprojectable binding refuses the whole index, and a caller wanting only the file-backed subset must filter the index first -- a statement of intent rather than a silent loss." + +fn msrp_artifact(path: String) -> Artifact { + Artifact { + kind: SourceFile, + id: ^msrp_fixture, + file_path: path, + } +} + +fn msrp_parsed(module_leaf: String, path: String) -> ModuleStorageBinding { + ModuleStorageBinding { + module: [^test, ^fixture, ^msrp], + provenance: ParsedFromSource { + artifact: msrp_artifact(path: path), + span_index: span_index_empty(), + }, + source_root: DagTree, + } +} + +fn msrp_other_parsed(path: String) -> ModuleStorageBinding { + ModuleStorageBinding { + module: [^test, ^fixture, ^msrp_other], + provenance: ParsedFromSource { + artifact: msrp_artifact(path: path), + span_index: span_index_empty(), + }, + source_root: V2Tree, + } +} + +fn msrp_produced() -> ModuleStorageBinding { + ModuleStorageBinding { + module: [^test, ^fixture, ^msrp_produced], + provenance: ProducedByBehavior { producer: ^msrp_producer }, + source_root: DagTree, + } +} + +fn msrp_second_produced() -> ModuleStorageBinding { + ModuleStorageBinding { + module: [^test, ^fixture, ^msrp_second_produced], + provenance: ProducedByBehavior { producer: ^msrp_other_producer }, + source_root: DagTree, + } +} + +// A parsed binding projects to a row carrying BOTH derived facts at exact values: the dotted module +// path and the artifact's own file_path. Asserting only that a row appeared would pass for a +// projection that produced a row about the wrong module or invented a path from the module name -- +// which is precisely the fabrication the refusal arm exists to prevent, so the positive row has to +// pin both sides. +test fn witness_parsed_binding_projects_module_and_its_artifact_path() -> Bool { + match module_path_file_row_from_storage_binding( + binding: msrp_parsed(module_leaf: "msrp", path: "dag/test/fixture/msrp.dag"), + ) { + ModulePathFileRowNoStorageRealization { module: _, producer: _ } => false + ModulePathFileRowProjected { row: row } => + (row.module_path as String) == "test.fixture.msrp" + && (row.file_path as String) == "dag/test/fixture/msrp.dag" + } +} + +// A produced module has no storage realization, and the projection must say so rather than answer +// with a path. Any path it could invent -- the module name reshaped, the empty string, the producer's +// own file -- is indistinguishable downstream from a real one. +test fn witness_produced_module_refuses_instead_of_inventing_a_path() -> Bool { + match module_path_file_row_from_storage_binding(binding: msrp_produced()) { + ModulePathFileRowProjected { row: _ } => false + ModulePathFileRowNoStorageRealization { module: _, producer: producer } => + (producer as String) == "msrp_producer" + } +} + +test fn witness_index_of_parsed_bindings_projects_every_row_in_order() -> Bool { + match module_path_file_rows_from_storage_index( + index: [ + msrp_parsed(module_leaf: "msrp", path: "dag/test/fixture/msrp.dag"), + msrp_other_parsed(path: "src/v2/test/fixture/msrp_other.dag"), + ], + ) { + ModulePathFileRowsFromIndexRefused { first_failure: _, more_failures: _ } => false + ModulePathFileRowsFromIndexReady { rows: rows } => + length(rows) == 2 + && msrp_first_row_path(rows: rows) == "dag/test/fixture/msrp.dag" + && msrp_last_row_path(rows: rows) == "src/v2/test/fixture/msrp_other.dag" + } +} + +// First and last rather than an index lookup, because ORDER is the property under assertion and the +// two ends are what a reversed or reordered fold would swap. +fn msrp_first_row_path(rows: List) -> String { + match first(rows) { + Absent => "" + Present { value: row } => row.file_path as String + } +} + +fn msrp_last_row_path(rows: List) -> String { + fold(rows, init: "", f: (acc, row) => row.file_path as String) +} + +// THE LOAD-BEARING ROW. Two projectable bindings sit either side of one that is not, so a projection +// that dropped the unprojectable one would return two rows and read as complete. The refusal must +// therefore reach the caller, not the survivors. +test fn witness_one_unprojectable_binding_refuses_the_whole_index() -> Bool { + match module_path_file_rows_from_storage_index( + index: [ + msrp_parsed(module_leaf: "msrp", path: "dag/test/fixture/msrp.dag"), + msrp_produced(), + msrp_other_parsed(path: "src/v2/test/fixture/msrp_other.dag"), + ], + ) { + ModulePathFileRowsFromIndexReady { rows: _ } => false + ModulePathFileRowsFromIndexRefused { first_failure: first, more_failures: more } => + length(more) == 0 && msrp_failure_producer_is(failure: first, producer: "msrp_producer") + } +} + +// Several unprojectable bindings are all reported, first plus the rest, so a count of the deficit is +// available. Reporting only the first would make the second invisible and the remedy partial. +test fn witness_every_unprojectable_binding_is_reported() -> Bool { + match module_path_file_rows_from_storage_index( + index: [msrp_produced(), msrp_second_produced()], + ) { + ModulePathFileRowsFromIndexReady { rows: _ } => false + ModulePathFileRowsFromIndexRefused { first_failure: first, more_failures: more } => + msrp_failure_producer_is(failure: first, producer: "msrp_producer") + && length(more) == 1 + && fold(more, init: false, f: (found, f) => + found || msrp_failure_producer_is(failure: f, producer: "msrp_other_producer")) + } +} + +fn msrp_failure_producer_is(failure: ModulePathFileRowProjection, producer: String) -> Bool { + match failure { + ModulePathFileRowProjected { row: _ } => false + ModulePathFileRowNoStorageRealization { module: _, producer: observed } => + (observed as String) == producer + } +} + +// An empty index is Ready with no rows, and that is NOT the narrow: nothing was observed and nothing +// is claimed, because no binding was supplied to lose. Stated as its own row so the distinction +// between "no input" and "input whose answer was dropped" stays visible. +test fn witness_empty_index_projects_no_rows() -> Bool { + match module_path_file_rows_from_storage_index(index: msrp_no_bindings()) { + ModulePathFileRowsFromIndexRefused { first_failure: _, more_failures: _ } => false + ModulePathFileRowsFromIndexReady { rows: rows } => length(rows) == 0 + } +} + +fn msrp_no_bindings() -> ModuleStorageIndex { Empty } diff --git a/dag/test/claim/namespace_pool_independence_witness_test.dag b/dag/test/claim/namespace_pool_independence_witness_test.dag index 6cd6914ae27..5ac5777d40c 100644 --- a/dag/test/claim/namespace_pool_independence_witness_test.dag +++ b/dag/test/claim/namespace_pool_independence_witness_test.dag @@ -45,7 +45,7 @@ data namespace_pool_independence_witness_note: String = "N3-C witnesses for gunb data npi_decoy_same_spelled_substrate_finding_note: String = "EXECUTED CONCLUSION (loyal-ram-550 routing 2026-08-07): colliding decoy fixture (app.provider + app.decoy both root-export helper) — perturbed B1 projection is AssembledClosureDependencyProjectionBindingRefused with ReferenceBindingProjectionAmbiguous on BOTH consumer references; no Ready projection, no alternate selection, no extra dependency edge. Substrate behaviour is CORRECT under namespace-only double-bound-is-error; prior equality expectation was wrong. Stronger Class B control: proves silent pool-driven reselection does NOT occur. OPEN (operator, non-blocking): CrossFileProviderExportedExposure => RootExposure may be wrong exposure model if cross-module access is projection (provider.helper) not bare-name root injection — if exposure model changes later this control remains as regression evidence." -data npi_cross_file_export_exposure_open_question_note: String = "OPEN QUESTION (loyal-ram-550 escalation 2026-08-07, operator-signed territory): fixture models cross-file provider export as CrossFileProviderExportedExposure => RootExposure, injecting helper into root scope. Namespace-only design says cross-module access is projection — sibling module visible, members reached as provider.helper, not bare-name injection. If projection governs, app.provider.helper and app.decoy.helper never collide at root and the decoy collision may be a fixture exposure artifact. Does not block N3-C; colliding_root_exported_homonym_refuses_ambiguity_holds stays enrolled regardless." +data npi_cross_file_export_exposure_resolved_note: String = "RESOLVED 2026-08-13 (operator ruling, gunbc 8115): projection governs. CrossFileProviderExportedExposure and its ModuleExposure => RootExposure promotion are DELETED; every ordinary module member grounds ModuleLocalMemberExposure whether its file is an entry or a provider, and a cross-module member is reached as provider.helper, never injected into root. The whole source-root universe is the declaration DENOMINATOR, not the visibility set. What the promotion cost, measured before removal: the accepted declarations for one file were a function of which file the walk started from -- identical bytes bound under one entry and refused under another. The colliding-decoy control below is retained as regression evidence exactly as its own note anticipated." fn npi_witness_consumer_file() -> FilePath { "app/consumer.dag" @@ -195,8 +195,8 @@ fn npi_reference_occurrence_in_transport( fn npi_colliding_decoy_row() -> CrossFileBindingClosureRow { cross_file_clause_e_fixture_unrelated_loaded_provider_row( declaration_authored_name: "helper", - module_dotted: "app.decoy", - file_path: "app/decoy.dag", + module_dotted: "other.provider", + file_path: "other/provider.dag", ) } diff --git a/dag/test/claim/type_reference_binding_context_witness_test.dag b/dag/test/claim/type_reference_binding_context_witness_test.dag index e05edffba8a..52fd6506806 100644 --- a/dag/test/claim/type_reference_binding_context_witness_test.dag +++ b/dag/test/claim/type_reference_binding_context_witness_test.dag @@ -12,6 +12,9 @@ import std.occurrence_identity { ReferenceOccurrence, OccurrenceCategory, TypeOccurrence, + ConstructorOccurrence, + FieldOccurrence, + LexicalValueOccurrence, CallableOccurrence, } import std.occurrence_binding_candidates { @@ -103,6 +106,7 @@ test fn module_root_containment_derives_root_exposure_holds() -> Bool { module_path: trbc_module_path(dotted: "std.content_hash"), containment: type_path, grounding: NamespaceStructuralRootExposure, + category: TypeOccurrence, ) { RootExposure => true _ => false @@ -116,12 +120,50 @@ test fn single_ancestor_containment_derives_module_exposure_holds() -> Bool { module_path: trbc_module_path(dotted: "extdeps.consumer"), containment: fn_path, grounding: NamespaceStructuralRootExposure, + category: TypeOccurrence, ) { ModuleExposure { module: module } => module == trbc_module_path(dotted: "extdeps.consumer") _ => false } } +// The discriminating pair for category-aware nesting. Both declarations sit at the SAME containment +// depth -- children of a type node, ancestors [module, type] -- and differ only in category, so +// containment shape alone cannot separate them. A constructor is reachable at module scope and must +// derive ModuleExposure; a field is not and must stay lexically scoped to the type body. Before this, +// both derived LexicalExposure and a module-scope reference to a constructor did not bind. +test fn nested_constructor_derives_module_exposure_holds() -> Bool { + let module_occ = OccurrenceId { value: 1 } + let type_occ = OccurrenceId { value: 2 } + let variant = OccurrenceId { value: 31 } + let nested_path = trbc_fixture_path(ancestors: [module_occ, type_occ], terminal: variant) + match declaration_exposure_from_containment( + module_path: trbc_module_path(dotted: "extdeps.consumer"), + containment: nested_path, + grounding: NamespaceStructuralRootExposure, + category: ConstructorOccurrence, + ) { + ModuleExposure { module: module } => module == trbc_module_path(dotted: "extdeps.consumer") + _ => false + } +} + +test fn nested_field_stays_lexically_scoped_holds() -> Bool { + let module_occ = OccurrenceId { value: 1 } + let type_occ = OccurrenceId { value: 2 } + let field = OccurrenceId { value: 32 } + let nested_path = trbc_fixture_path(ancestors: [module_occ, type_occ], terminal: field) + match declaration_exposure_from_containment( + module_path: trbc_module_path(dotted: "extdeps.consumer"), + containment: nested_path, + grounding: NamespaceStructuralRootExposure, + category: FieldOccurrence, + ) { + LexicalExposure { exposing_scope: scope } => scope.terminal == type_occ + _ => false + } +} + test fn nested_containment_derives_lexical_exposure_holds() -> Bool { let outer = OccurrenceId { value: 1 } let inner = OccurrenceId { value: 2 } @@ -131,6 +173,7 @@ test fn nested_containment_derives_lexical_exposure_holds() -> Bool { module_path: trbc_module_path(dotted: "extdeps.consumer"), containment: nested_path, grounding: NamespaceStructuralRootExposure, + category: LexicalValueOccurrence, ) { LexicalExposure { exposing_scope: scope } => scope.terminal == inner && (scope.ancestors |> count) == 1 diff --git a/dag/test/fixture/cross_file_clause_e_fixture.dag b/dag/test/fixture/cross_file_clause_e_fixture.dag index 5879d7b21d5..54a10dad19e 100644 --- a/dag/test/fixture/cross_file_clause_e_fixture.dag +++ b/dag/test/fixture/cross_file_clause_e_fixture.dag @@ -22,7 +22,7 @@ import std.occurrence_binding_candidates { DeclarationExposureRow, assemble_cross_file_binding_closure, declaration_exposure_from_containment, - CrossFileProviderExportedExposure, + ModuleLocalMemberExposure, } data cross_file_clause_e_fixture_note: String = "Controlled cross-file clause-(e) fixture for witness tests only — hand-built transports exercising projection law. NOT production authority; production admissions must consume parser-produced closures via v1.gunbc.namespace_reference_derived_closure_clause_e_production." @@ -79,8 +79,8 @@ fn cross_file_clause_e_fixture_consumer_transport() -> OccurrenceTransport { OccurrenceTransport { index: OccurrenceIndex { entries: [ - cross_file_clause_e_fixture_index_entry(occurrence: ref_one, authored_name: "helper"), - cross_file_clause_e_fixture_index_entry(occurrence: ref_two, authored_name: "helper"), + cross_file_clause_e_fixture_index_entry(occurrence: ref_one, authored_name: "provider.helper"), + cross_file_clause_e_fixture_index_entry(occurrence: ref_two, authored_name: "provider.helper"), ], }, declarations: [], @@ -150,7 +150,8 @@ fn cross_file_clause_e_fixture_provider_row() -> CrossFileBindingClosureRow { exposure: declaration_exposure_from_containment( module_path: provider_module, containment: helper_path, - grounding: CrossFileProviderExportedExposure, + grounding: ModuleLocalMemberExposure, + category: LexicalValueOccurrence, ), }, ], @@ -210,7 +211,8 @@ fn cross_file_clause_e_fixture_unrelated_loaded_provider_row( exposure: declaration_exposure_from_containment( module_path: provider_module, containment: decl_path, - grounding: CrossFileProviderExportedExposure, + grounding: ModuleLocalMemberExposure, + category: LexicalValueOccurrence, ), }, ], diff --git a/docs/plans/import-strip-witness-discovery-cascade-diagnosis.md b/docs/plans/import-strip-witness-discovery-cascade-diagnosis.md index fdcecbd71e5..c8609ed1596 100644 --- a/docs/plans/import-strip-witness-discovery-cascade-diagnosis.md +++ b/docs/plans/import-strip-witness-discovery-cascade-diagnosis.md @@ -1103,3 +1103,226 @@ imports to close fail-open pool reliance under *today's* compiler: those are interim containment and need a dissolution trigger tied to the production changeover, or the temporary mechanism hardens into the concept this lane exists to delete. + +## 16. Independent corroboration of pool-membership coincidence (relayed, 2026-08-10) + +Class B says a stripped file's own bare cross-module references resolve only by +**pool-membership coincidence** — `resolve_in` finds the target exactly when some +unrelated import elsewhere in the assembled closure has already dragged it into +the pool, never from the bare-reference closure itself binding it. Until now the +evidence for that was this lane's own probes, which is a single observer. + +Three corroborations arrived from other lanes on one day, relayed via +witty-raven-412. They are recorded here **as relayed, not as observations of +mine** — I did not run any of them, and none of the three was constructed to test +this claim, which is precisely what makes them worth keeping: + +- wise-moth — bare slice-1 references broke on their first real dispatch. +- valiant-boar — parse-wall hypotheses were confounded by closure membership. +- a production entry, `fleet_converge_cli`, failed to compile **on main** through + its own collect entry because nothing dragged its dependencies into the pool. + +The third is the strongest of the three and the one that changes the claim's +standing. Every prior specimen was a *stripped* file, so "coincidence" could be +read as an artifact of stripping. `fleet_converge_cli` is an ordinary production +entry on main with its imports intact, and it still failed for want of pool +membership — so the coincidence is a property of how the pool is assembled, not a +consequence of removing imports. Stripping does not create the defect; it removes +the accidental cover that hides it. + +What this does NOT establish, stated because the temptation is to over-read three +agreeing reports: none of them measures the size of the affected population, none +identifies which references are covered by coincidence today, and a relayed +summary is weaker evidence than a run. The blocking condition on further +`dag/**` import-stripping is unchanged, and so is its discharge — a +closure-independent binding fix, or a provable-coverage construction check. + +## 17. The import gate: a two-sided control for the dependency-producer cutover + +Measured by deep-fox-168 on the live tree, 2026-08-10, and reproduced here as the +control for replacing `dependency_resolution_facts_live`'s body. Recorded verbatim +rather than paraphrased, because the probe's validity turns on one detail that is +easy to lose. + +**The mechanism, verified in code** (`src/v1/stage0/src/cli_run.rs`, +`build_both_closure_edge_index`). Per source file the loop inserts `ref_out`, then: + +```rust +if source_declares_import_lines(&sf.content) { + continue; +} +bare_scan_eligible.insert(file_rel.clone()); +``` + +and `source_declares_import_lines` is a raw text scan — `any line +trim_start().starts_with("import ")`. So **declaring any import at all switches off +a file's entire bare-reference scan.** An import-free file gets full bare coverage; +a fully-imported file is covered by `ref_out`; only the mixture falls through the +gap. `dag/std/syntax.dag` declares zero imports and its emitted `std_syntax.rs` +contains `crate::std_types`, so that real dependency is discoverable *only* through +the bare scan. + +**Exact reproduction (~30s).** + +1. `dag/probe_tmp/entry.dag`: + + ``` + module probe_tmp.entry + + import std.syntax { LiteralValue, LitBool } + + fn probe() -> LiteralValue { + LitBool { value: true } + } + ``` + +2. Baseline, `syntax.dag` untouched — observed **resolved 5 sources**: + + ``` + gunbc run --source-root dag --source-root src/v2 \ + --entry dag/probe_tmp/entry.dag --function probe + ``` + +3. `dag/probe_tmp/leaf.dag` — **the zero-dependency module is what makes the test + valid**: + + ``` + module probe_tmp.leaf + + type ProbeLeaf + = ProbeLeafOnly + | ProbeLeafOther + ``` + +4. Insert exactly one line into `dag/std/syntax.dag` after the module line: + `import probe_tmp.leaf { ProbeLeaf }` + +5. Re-run the same command — observed **resolved 3 sources**. + +6. Revert `syntax.dag`, delete `dag/probe_tmp`. + +**Why attempt 1 could not fail, kept so nobody rebuilds it.** The first attempt +added `import std.nat`, and still resolved 5 sources. `std.nat` transitively imports +`std.types` — so the probe *supplied* the very dependency it was testing for and had +no failing branch. Any import added here must pull nothing transitively. Substituting +a real module for `leaf.dag` without checking its transitive closure silently +recreates the cannot-fail form. + +**The bound — SUPERSEDED, see §17.2.** This paragraph originally read that a build +break was NOT established. That is false, and the correction is recorded rather than +silently patched because the wrong version was banked here first: a hard resolution +failure has since been demonstrated. The `5 → 3` figure is also NET rather than a +count of departures — the perturbation adds `leaf.dag` while three modules leave. +Read §17.2, not this paragraph, for the measured claim. + +**Why this is the cutover control and not someone else's fix.** It is two-sided. +Under the old import-derived producer the perturbation MUST shrink the closure by +two; under the accepted-binding-derived producer it MUST change nothing, because the +file's own reference set never changed. A hand-authored disagreement only asserts the +new behaviour; this runs in both directions. It is also the §Class B blocker that +gates further `dag/**` stripping — and the cutover does not patch it, it dissolves +it, because once dependencies derive from accepted bindings the text-scan predicate +is no longer on the path. + +**Three live specimens** of the import-free class, measured: `dag/std/syntax.dag`, +`dag/std/coercion.dag`, `dag/std/http_path.dag` — all declare zero imports, all +reference `std_types`. + +**One false-positive class to pre-empt.** v2 compiler modules that appear to +reference without importing (`03_normalize` → `usv_pilot_v2_std_algebra` and +friends) are artifacts of naive name comparison: `usv_pilot_v2_std_algebra.rs` is the +emitted realization of `v2.std.algebra`, which those files DO import explicitly. +Comparing `.dag` import names against emitted `use crate::` names without mapping the +`usv_pilot_` prefix produces four phantom findings. + +### 17.1 How the count is read, and why the count is not enough for item 7 + +**Reading it.** No instrumentation and no flag: `gunbc run` prints `resolved N +sources` on stdout before `compile.frontend` starts, read with `grep -E '^resolved'` +on the run output, baseline then perturbed, same entry and same source roots both +times. That is part of why the probe is cheap. + +**The count is closure SIZE, not IDENTITY — and item 7 needs identity.** `5 → 3` +establishes that two modules left; it does not name them. A classification step whose +job is to say *which* edges the old and new producers disagree about would pass on a +coincidental `5 → 3` that dropped two different modules. For the existence claim the +count is sufficient and that is all it was collected for; for the disagreement +classifier the control must read the **edge set**, not the cardinality. This is +recorded as a required strengthening of the instrument, not a caveat on the finding. + +**Order of operations.** Same entry, same source roots, and `dag/std/syntax.dag` +reverted between runs — the edit is to a live std file, so a leaked edit poisons +anything else running against that tree. + +**Two claims, labelled differently on purpose.** + +| side | status | +|---|---| +| OLD import-derived producer: perturbation shrinks the closure 5 → 3 | **OBSERVED** (deep-fox-168, live tree, 2026-08-10) | +| NEW binding-derived producer: perturbation changes nothing | **PREDICTION** — unobservable until item 7 exists to run it against | + +The two-sidedness is what makes this the right control, but only one side has been +run. Writing them as one measured fact would be exactly the inflation this lane keeps +catching. + +**Available, not yet requested:** a narrower entry that actually reaches a +`std_types`-dependent declaration inside `std.syntax`, which would settle whether the +closure shrink produces a hard resolution failure — the difference between *one +import shrinks the closure* and *one import breaks the build*. + +### 17.2 CORRECTED — the closure shrink DOES break resolution, and the first probe was invalid + +**This section replaces an earlier version that reported the opposite.** That version +said a live attempt to break the build had failed to break it, and it was wrong: the +probe never reached its target. The wrong version is not preserved above, because a +falsified claim sitting in the canonical record is what §3's stale-citation class +costs; what is preserved is the fact that it was made, and why it misled. + +Measured by deep-fox-168, 2026-08-10, after closing the identity residue §17.1 said +was required. Closing it inverted the earlier conclusion — which is the strongest +argument in this document for why cardinality is not identity. + +**The departed set is THREE modules, and it is now named.** `std.types`, +`std.algebra` (imported by types), `std.error_primitives` (imported by algebra). +Baseline 5 = entry + syntax + types + algebra + error_primitives. Perturbed 3 = entry ++ syntax + **leaf**. So `5 → 3` was a NET figure: three modules leave and the probe's +own `leaf.dag` arrives. A cardinality that had not been decomposed. + +**Membership tested directly**, since no source-listing flag exists: an entry that +declares an import (so its own bare scan is off) making a bare reference to +`NonEmptyStr`, which only `std.types` provides — it resolves iff `std_types` is in the +pool. + +| run | result | +|---|---| +| baseline (5 sources) | resolves | +| perturbed (3 sources) | **`error: unresolved type 'NonEmptyStr'`** | + +**Why the earlier probe was invalid.** `probe2` exercised `algebra_field_entries`, +typed `List`. `std_types` *is* among the departed modules, so +probe2 returning 8 at 3 sources was not `std.types` surviving — it was `List` being +reachable without `std.types` in the closure at all. Its null result says nothing +about the module it aimed at. The accurate description of probe2 is **a live attempt +that did not reach its target**, not one that failed to break anything. + +**The measured claim, in the form that should travel.** + +> One import edit to an import-free file silently removes that file's bare-reference +> dependencies from the closure (measured: `std.types`, `std.algebra`, +> `std.error_primitives`). Programs reaching those modules by bare reference then fail +> to resolve (measured: `unresolved type NonEmptyStr`). Programs that import them +> directly are unaffected. An earlier probe that appeared to show no failure was +> invalid — it exercised `List`, which resolves without `std.types` in the closure. + +**The qualifier is the part that matters to this lane.** Breakage is confined to +references that depend on pool coincidence — a program that properly imports +`std.types` supplies it itself and is untouched. **Import deletion produces more of +exactly that pattern**, so exposure to this class GROWS as this lane proceeds. It is +not a latent hazard that stripping happens to reveal; it is a hazard stripping +enlarges, which is a stronger reason to close it in the cutover rather than around it. + +**Two-directional disagreement, with a concrete instance on each arm.** The old +producer DROPS real dependencies (these three, now named) and evidently CARRIES +modules nothing needs (probe2 resolved `List` with none of them present). The +classifier catches the first arm as a build break eventually; the second never fails +anything and only costs closure size forever — the absorbing shape §5 names. diff --git a/src/v1/02_parse.dag b/src/v1/02_parse.dag index 51e15057410..51209e83a8d 100644 --- a/src/v1/02_parse.dag +++ b/src/v1/02_parse.dag @@ -68,7 +68,9 @@ import std.occurrence_identity { CallableOccurrence, NamespaceSegmentOccurrence, FieldOccurrence, + ConstructorOccurrence, MethodOccurrence, + ModuleValueOccurrence, authored_token_ordinal_space_from_allocator, occurrence_id_allocator_initial, occurrence_id_allocator_advance_to, @@ -1099,7 +1101,7 @@ fn stamp_parsed_pattern( nodes: field_bindings, ancestors: ancestors, ctx: ctx, - role: ParsedOccurrenceDeclaration { category: FieldOccurrence } + role: ParsedOccurrenceReference { category: FieldOccurrence } ) ParsedPatternStampResult { pattern: Present { @@ -1180,6 +1182,74 @@ fn parsed_occurrence_role_for_node( } } +// A module item that introduces a TYPE NAME. This is deliberately NOT `infer_items` `item_kind`, and +// is not a second copy of it: `item_kind` answers which item FORM a module item has, and its TypeItem +// arm keys on `connective != NoConnective`, which is true of anonymous inline record nodes and false +// of a type ALIAS (`type X = Other`, which parse authors as NoConnective with `inferred` Present). The +// question here is different — does this node bind a type name into its module — so both shapes must +// answer yes and an unnamed node must answer no. Changing `item_kind` to agree would move the export +// classification of every alias in the corpus, which is a separate decision with its own receipt. +// +// This is only ever asked of a node reached AS A MODULE ITEM. It cannot be asked shape-first: a +// product FIELD node carries `inferred` Present with a Present ident_span, and a payload VARIANT node +// carries `connective: Conj` with a Present ident_span, so both would answer yes on shape alone. That +// is why minting is role-directed below rather than folded into parsed_occurrence_role_for_node. +fn parsed_module_item_declares_type_name(item: Node) -> Bool { + item.transport == none + && item.body == none + && item.ident_span != none + && item.name != "" + && (item.connective != NoConnective || item.inferred != none) +} + +// A module-scope `data name: T = v`. parsed_occurrence_role_for_node classifies it Unclassified +// because it carries BOTH a body and a type annotation, and that shape is not safe to widen there: +// the same shape is reachable from nodes that are not module items. Asked role-directed, of a node +// already known to be a module item, it is decidable -- which is why the arm lives here and not in +// the shape classifier. Before this arm existed the item bound no name, so a cross-file reference to +// any module datum could not bind and the derived closure was short by every module-value edge. +fn parsed_module_item_declares_value_name(item: Node) -> Bool { + item.transport == none + && item.body != none + && item.type_annotation != none + && item.ident_span != none + && item.name != "" +} + +fn parsed_module_item_role(item: Node) -> ParsedOccurrenceRole { + if parsed_module_item_declares_type_name(item: item) { + ParsedOccurrenceDeclaration { category: TypeOccurrence } + } else if parsed_module_item_declares_value_name(item: item) { + ParsedOccurrenceDeclaration { category: ModuleValueOccurrence } + } else { + ParsedOccurrenceUnclassified + } +} + +// Module items are heterogeneous, so their roles cannot come from one list-wide role the way a +// parameter list's can. Each item's role is computed from the item itself. +fn stamp_parsed_module_item_list( + nodes: List, + ancestors: FreeMonoid, + ctx: ParseContext +) -> ParsedNodeListStampResult { + fold( + nodes, + init: ParsedNodeListStampResult { nodes: [], ctx: ctx }, + f: (acc, node) => + let stamped = stamp_parsed_node( + node: node, + ancestors: ancestors, + ctx: acc.ctx, + role: parsed_module_item_role(item: node) + ) + ParsedNodeListStampResult { + nodes: list_push(acc.nodes, stamped.node), + ctx: stamped.ctx, + } + ) +} + fn parse_context_record_occurrence( ctx: ParseContext, role: ParsedOccurrenceRole, @@ -1257,6 +1327,23 @@ fn stamp_parsed_node_list_with_head_role( } } +// Minting is role-directed here rather than shape-inferred per node, because the member kind of a +// type declaration's child is a fact about the PARENT: a product's children are fields, a coproduct's +// are constructors, an alias has none. The shapes themselves do not separate — a product FIELD node +// and its owning type declaration share `body: Absent`, and a payload VARIANT node carries the same +// `connective: Conj` a product declaration does. Directing the role from the parent is what keeps a +// field from arriving Unclassified and being re-classified as the declaration above it. +// +// Two arms are deliberately not forked. The module root's children ARE its items, and only there is +// an item's own shape a safe discriminator (see parsed_module_item_declares_type_name). And a nullary +// constructor takes the same arm as a payload one — it simply has no children — so the rule does not +// fork on arity. +// The ModuleValueOccurrence arm shares the lexical binder's child shape -- `data name: T = v` carries +// its type annotation as head and its value expression as tail -- and needs its own arm rather than +// the catch-all, which stamps every child Unclassified: that would make the type annotation an +// ordinary value reference and lose the reference the value expression carries. Measured: without the +// arm the provider file's own constructor reference stops binding, so admitting a file did not admit +// what that file declares. fn stamp_parsed_node_children( node: Node, ancestors: FreeMonoid, @@ -1297,6 +1384,51 @@ fn stamp_parsed_node_children( head_role: ParsedOccurrenceReference { category: TypeOccurrence }, tail_role: ParsedOccurrenceUnclassified ) + ParsedOccurrenceDeclaration { category: ModuleValueOccurrence } => + stamp_parsed_node_list_with_head_role( + nodes: node.children, + ancestors: ancestors, + ctx: ctx, + head_role: ParsedOccurrenceReference { category: TypeOccurrence }, + tail_role: ParsedOccurrenceUnclassified + ) + ParsedOccurrenceDeclaration { category: NamespaceSegmentOccurrence } => + stamp_parsed_module_item_list( + nodes: node.children, + ancestors: ancestors, + ctx: ctx + ) + ParsedOccurrenceDeclaration { category: TypeOccurrence } => + match node.connective { + Disj => + stamp_parsed_node_list( + nodes: node.children, + ancestors: ancestors, + ctx: ctx, + role: ParsedOccurrenceDeclaration { category: ConstructorOccurrence } + ) + Conj => + stamp_parsed_node_list( + nodes: node.children, + ancestors: ancestors, + ctx: ctx, + role: ParsedOccurrenceDeclaration { category: FieldOccurrence } + ) + _ => + stamp_parsed_node_list( + nodes: node.children, + ancestors: ancestors, + ctx: ctx, + role: ParsedOccurrenceUnclassified + ) + } + ParsedOccurrenceDeclaration { category: ConstructorOccurrence } => + stamp_parsed_node_list( + nodes: node.children, + ancestors: ancestors, + ctx: ctx, + role: ParsedOccurrenceDeclaration { category: FieldOccurrence } + ) _ => stamp_parsed_node_list( nodes: node.children, diff --git a/src/v1/gunbc/namespace_reference_derived_closure_production_observations.dag b/src/v1/gunbc/namespace_reference_derived_closure_production_observations.dag index 6d0517b1e2c..a5269977ed4 100644 --- a/src/v1/gunbc/namespace_reference_derived_closure_production_observations.dag +++ b/src/v1/gunbc/namespace_reference_derived_closure_production_observations.dag @@ -2,6 +2,7 @@ module v1.gunbc.namespace_reference_derived_closure_production_observations import std.occurrence_identity { OccurrenceTransport, OccurrenceId } import std.occurrence_binding_candidates { + ModuleLocalMemberExposure, OccurrenceBindingCandidateInputs, } import std.reference_binding_observation { @@ -51,6 +52,7 @@ fn nrdfc_parse(file: String, source: String) -> NrdfcParsed { inputs: occurrence_binding_inputs_from_transport( module_path: module_path, transport: transport, + grounding: ModuleLocalMemberExposure, ), } } diff --git a/src/v1/gunbc/occurrence_binding_parser_walk.dag b/src/v1/gunbc/occurrence_binding_parser_walk.dag index 7969c367cd4..570a4152db3 100644 --- a/src/v1/gunbc/occurrence_binding_parser_walk.dag +++ b/src/v1/gunbc/occurrence_binding_parser_walk.dag @@ -14,6 +14,7 @@ import std.occurrence_binding_candidates { DeclarationExposureRow, AuthoredOrderRow, OccurrenceBindingCandidateInputs, + DeclarationExposureGrounding, ModuleLocalMemberExposure, declaration_exposure_from_containment, } @@ -70,9 +71,16 @@ fn authored_order_row_from_entry(entry: OccurrenceIndexEntry) -> AuthoredOrderRo data occurrence_binding_inputs_accumulator_note: String = "Input projection accumulates by prepend during fold and reverses once at the end — never concat(acc, [row]) (§6 bare-minimum cost / blocker 6)." +data occurrence_binding_inputs_grounding_note: String = "THE GROUNDING IS A PARAMETER BECAUSE IT IS A FACT ABOUT THE FILE'S ROLE IN THE CLOSURE, NOT ABOUT THE PARSE. It was hardwired to ModuleLocalMemberExposure, which derives ModuleExposure for module-root declarations — visible only to references in the SAME module. That is right for a consumer's own declarations and wrong for a PROVIDER in a cross-file closure, whose exported declarations must derive RootExposure. With it hardwired, an import-less consumer could never bind to a declaration in another file: the candidate population was empty by construction, and the failure looked like an ordinary Unbound rather than a missing exposure grounding. + +Found by execution, not by reading: the checkpoint-1 bridge witness resolved a real LiveTreeDisposition reference against the real v2.std.live_tree module and got Unbound while its provider-withheld control behaved correctly, which is the signature of the provider being present but unexposed. + +DeclarationExposure's own authority requires the grounding be chosen AT DERIVATION, never as a post-hoc upgrade of an already-derived row, which is exactly why this is a parameter here rather than a rewrite of the rows afterwards. Every caller passes ModuleLocalMemberExposure for an ordinary module member, whether the file is the entry or a provider: exposure is a fact about the declaration's own containment, never about the role the file plays in one closure, and role-stamped exposure made a file's accepted declarations depend on which file was the entry." + fn occurrence_binding_inputs_from_transport( module_path: NonEmptyStr, - transport: OccurrenceTransport + transport: OccurrenceTransport, + grounding: DeclarationExposureGrounding ) -> OccurrenceBindingCandidateInputs { OccurrenceBindingCandidateInputs { module_paths: reverse(fold( @@ -96,7 +104,8 @@ fn occurrence_binding_inputs_from_transport( exposure: declaration_exposure_from_containment( module_path: module_path, containment: declaration.containment, - grounding: ModuleLocalMemberExposure, + grounding: grounding, + category: declaration.category, ), }, ], acc), diff --git a/src/v1/stage0/src/bin/namespace_structural_root_exposure_generated_witness.rs b/src/v1/stage0/src/bin/namespace_structural_root_exposure_generated_witness.rs index 57d307f079b..b4fac1b48f8 100644 --- a/src/v1/stage0/src/bin/namespace_structural_root_exposure_generated_witness.rs +++ b/src/v1/stage0/src/bin/namespace_structural_root_exposure_generated_witness.rs @@ -16,7 +16,9 @@ use std::rc::Rc; use v1_compiler::std_occurrence_binding_candidates::{ declaration_exposure_from_containment, DeclarationExposure, DeclarationExposureGrounding, }; -use v1_compiler::std_occurrence_identity::{OccurrenceContainmentPath, OccurrenceId}; +use v1_compiler::std_occurrence_identity::{ + OccurrenceCategory, OccurrenceContainmentPath, OccurrenceId, +}; fn fail(msg: impl std::fmt::Display) -> ExitCode { eprintln!("namespace_structural_root_exposure_generated_witness: {msg}"); @@ -44,6 +46,7 @@ fn main() -> ExitCode { module_path.clone(), containment(&[], 1), DeclarationExposureGrounding::NamespaceStructuralRootExposure, + OccurrenceCategory::TypeOccurrence, ); match &*empty { DeclarationExposure::RootExposure => println!("PASS empty_ancestors_root_exposure"), @@ -60,6 +63,7 @@ fn main() -> ExitCode { module_path.clone(), containment(&[10], 11), DeclarationExposureGrounding::NamespaceStructuralRootExposure, + OccurrenceCategory::TypeOccurrence, ); match &*one { DeclarationExposure::ModuleExposure { module } if *module == module_path => { @@ -80,6 +84,11 @@ fn main() -> ExitCode { module_path.clone(), containment(&[20, 21], 22), DeclarationExposureGrounding::NamespaceStructuralRootExposure, + // A lexical binder, not a type: nesting alone no longer decides this arm. A declaration + // nested inside another is lexically scoped only when its category is not module-scope + // exposed, so a nested TYPE or CONSTRUCTOR now derives ModuleExposure and would fail here + // for the right reason. + OccurrenceCategory::LexicalValueOccurrence, ); match &*two { DeclarationExposure::LexicalExposure { exposing_scope } => { diff --git a/src/v1/stage0/src/bin/reference_derived_closure_probe.rs b/src/v1/stage0/src/bin/reference_derived_closure_probe.rs new file mode 100644 index 00000000000..68f54bf19b0 --- /dev/null +++ b/src/v1/stage0/src/bin/reference_derived_closure_probe.rs @@ -0,0 +1,68 @@ +#![allow(clippy::disallowed_macros)] + +use std::process::ExitCode; + +use v1_compiler::cli_run::reference_derived_closure_over_source_roots; + +// ONLY A SETTLED CLOSURE IS A SUCCESSFUL RUN. An earlier version of this bin printed the refusal and +// exited zero, which makes it unusable as a gate and reads as the ordinary path having ACCEPTED the +// closure. Refusal and exhaustion are failures of the run; a bad invocation is distinct from both. +fn main() -> ExitCode { + let args: Vec = std::env::args().collect(); + let mut source_roots: Vec = Vec::new(); + let mut entry: Option = None; + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "--source-root" => { + i += 1; + match args.get(i) { + Some(v) => source_roots.push(v.clone()), + None => { + eprintln!( + "reference_derived_closure_probe: --source-root requires a value" + ); + return ExitCode::from(2); + } + } + } + "--entry" => { + i += 1; + match args.get(i) { + Some(v) => entry = Some(v.clone()), + None => { + eprintln!("reference_derived_closure_probe: --entry requires a value"); + return ExitCode::from(2); + } + } + } + other => { + eprintln!( + "reference_derived_closure_probe: unknown argument: {}", + other + ); + return ExitCode::from(2); + } + } + i += 1; + } + let entry = match entry { + Some(e) => e, + None => { + eprintln!("reference_derived_closure_probe: --entry is required"); + return ExitCode::from(2); + } + }; + if source_roots.is_empty() { + eprintln!("reference_derived_closure_probe: provide at least one --source-root"); + return ExitCode::from(2); + } + let run = reference_derived_closure_over_source_roots(&entry, &source_roots); + if run.is_settled() { + println!("{}", run.report()); + ExitCode::SUCCESS + } else { + eprintln!("{}", run.report()); + ExitCode::from(1) + } +} diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 85c3799337e..61ccc188e32 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -1588,6 +1588,399 @@ fn for_each_parsed_module_binding( } } +/// Render EVERY failing projection in a refused population by class, not just its head. +fn summarize_population_failures( + population: &crate::std_occurrence_binding_candidates::BoundReferencePopulation, +) -> String { + use crate::std_occurrence_binding_candidates::{ + BoundReferencePopulation, ReferenceBindingProjection, + }; + let class_of = |p: &ReferenceBindingProjection| -> &'static str { + match p { + ReferenceBindingProjection::ReferenceBindingProjectionBound { .. } => "Bound", + ReferenceBindingProjection::ReferenceBindingProjectionUnbound { .. } => "Unbound", + ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { .. } => "Ambiguous", + ReferenceBindingProjection::ReferenceBindingProjectionTransportRefused { .. } => { + "TransportRefused" + } + ReferenceBindingProjection::ReferenceBindingProjectionModulePathRefused { .. } => { + "ModulePathRefused" + } + ReferenceBindingProjection::ReferenceBindingProjectionExposureRefused { .. } => { + "ExposureRefused" + } + ReferenceBindingProjection::ReferenceBindingProjectionAuthoredOrderRefused { + .. + } => "AuthoredOrderRefused", + ReferenceBindingProjection::ReferenceBindingProjectionDeclarationBucketRefused { + .. + } => "DeclarationBucketRefused", + ReferenceBindingProjection::ReferenceBindingProjectionModulePathMissing { .. } => { + "ModulePathMissing" + } + ReferenceBindingProjection::ReferenceBindingProjectionWrongCategory { .. } => { + "WrongCategory" + } + } + }; + match population { + BoundReferencePopulation::AllReferencesBound { .. } => "AllReferencesBound".to_string(), + BoundReferencePopulation::ReferencePopulationRefused { + first_failure, + more_failures, + } => { + let mut counts: std::collections::BTreeMap<&'static str, usize> = + std::collections::BTreeMap::new(); + *counts.entry(class_of(first_failure)).or_insert(0) += 1; + for failure in more_failures.iter() { + *counts.entry(class_of(failure)).or_insert(0) += 1; + } + counts + .into_iter() + .map(|(class, n)| format!("{class}x{n}")) + .collect::>() + .join(",") + } + } +} + +/// A candidate source file this universe could not admit. Every arm is a reason the DENOMINATOR is +/// incomplete, which is why none of them may be counted and skipped. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CandidateSourceFailure { + SourceReadFailed { + file: String, + cause: String, + }, + ParseRefused { + file: String, + }, + DuplicateCandidateFile { + file: String, + first_module: String, + conflicting_module: String, + }, + DuplicateModuleIdentity { + module_path: String, + first_file: String, + conflicting_file: String, + }, +} + +/// The outcome of running the modeled closure. Three states, and the caller decides — this type exists +/// so that a refusal cannot be rendered as a successful run by whoever formats the string. +#[derive(Debug, Clone)] +pub enum ReferenceDerivedClosureRun { + Settled { + entry: String, + universe: usize, + files: usize, + edges: usize, + provenance: usize, + iterations: i64, + }, + Refused { + entry: String, + universe: usize, + detail: String, + iterations: i64, + }, + Exhausted { + entry: String, + universe: usize, + files: usize, + iterations: i64, + }, + UniverseRefused { + first_failure: CandidateSourceFailure, + more_failures: Vec, + }, + EntryOutsideUniverse { + entry: String, + universe: usize, + }, +} + +impl ReferenceDerivedClosureRun { + /// True ONLY for a settled closure. Every other state is a failed run, including a universe that + /// could not be completed and an entry that is not in it. + pub fn is_settled(&self) -> bool { + matches!(self, ReferenceDerivedClosureRun::Settled { .. }) + } + + pub fn report(&self) -> String { + match self { + ReferenceDerivedClosureRun::Settled { entry, universe, files, edges, provenance, iterations } => format!( + "entry={entry} universe={universe} :: Settled: {files} file(s) admitted, {edges} edge(s), {provenance} provenance row(s), {iterations} iteration(s)" + ), + ReferenceDerivedClosureRun::Refused { entry, universe, detail, iterations } => format!( + "entry={entry} universe={universe} :: Refused after {iterations} iteration(s): {detail}" + ), + ReferenceDerivedClosureRun::Exhausted { entry, universe, files, iterations } => format!( + "entry={entry} universe={universe} :: Exhausted: {files} file(s) admitted after {iterations} iteration(s) (the closure was still growing)" + ), + ReferenceDerivedClosureRun::UniverseRefused { first_failure, more_failures } => format!( + "CandidateUniverseRefused: {first_failure:?} (+{} further failure(s))", + more_failures.len() + ), + ReferenceDerivedClosureRun::EntryOutsideUniverse { entry, universe } => format!( + "EntryOutsideUniverse: {entry} is not among the {universe} candidate file(s)" + ), + } + } +} + +/// Run the modeled reference-derived closure fixed point over an ordinary source-file universe. +/// +/// THE UNIVERSE IS THE DENOMINATOR FOR EVERY BINDING DECISION, so its construction is total and refuses. +/// An earlier version of this function pushed unreadable and parse-refused files onto a counter, omitted +/// them, and ran the closure anyway — which is unsound in the exact way this lane keeps closing: a +/// dropped file may hold the ONLY declaration answering a reference, so its omission renders as `Unbound` +/// (or, worse, leaves a surviving homonym uniquely selected and silently binds the wrong declaration), +/// and no caller can distinguish *the complete universe has no provider* from *the provider was in a file +/// we discarded*. Nothing starts until every candidate row is admitted under the declared scope. +/// +/// Duplicate file identity and duplicate module identity refuse for the same reason: two rows claiming +/// one file made the answer depend on which helper read the population first, and two files claiming one +/// module path make the module→file join ambiguous. +/// +/// The exposure grounding here is still the static entry/provider split, which is WRONG once a provider +/// becomes an admitted consumer with references of its own — that repair is relational exposure and lands +/// with the A→B→C transitive controls, not in this function. +pub fn reference_derived_closure_over_source_roots( + entry: &str, + source_roots: &[String], +) -> ReferenceDerivedClosureRun { + use crate::std_occurrence_binding_candidates::{ + cross_file_binding_closure_row, DeclarationExposureGrounding, + }; + use crate::std_reference_derived_closure::{ + reference_derived_closure_fixed_point, ParsedFileSupply, ReferenceDerivedClosureFixedPoint, + }; + use crate::v1_gunbc_occurrence_binding_parser_walk::{ + occurrence_binding_inputs_from_transport, parse_authored_occurrence_binding_source, + ParsedOccurrenceBindingSource, + }; + + let entry_key = module_index_path_key(Path::new(entry)); + let mut universe: Vec> = Vec::new(); + let mut failures: Vec = Vec::new(); + let mut file_seen: std::collections::HashMap = std::collections::HashMap::new(); + let mut module_seen: std::collections::HashMap = + std::collections::HashMap::new(); + let mut entry_seen = false; + + for root in source_roots { + let anchored_root = anchor_source_root(root); + let mut dag_files = Vec::new(); + collect_dag_files(Path::new(&anchored_root), &mut dag_files); + for path in dag_files { + let rel = module_index_path_key(&path); + let source = match std::fs::read_to_string(&path) { + Ok(text) => text, + Err(e) => { + failures.push(CandidateSourceFailure::SourceReadFailed { + file: rel, + cause: e.to_string(), + }); + continue; + } + }; + if rel == entry_key { + entry_seen = true; + } + // Exposure derives from the declaration's own containment, never from the role its file + // plays in this closure: a role-stamped grounding made the accepted declaration for a + // file a function of which file the walk started from. + let grounding = DeclarationExposureGrounding::ModuleLocalMemberExposure; + match &*parse_authored_occurrence_binding_source(rel.clone(), source) { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => { + failures.push(CandidateSourceFailure::ParseRefused { file: rel }); + } + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport, + module_path, + } => { + if let Some(first_module) = file_seen.get(&rel) { + failures.push(CandidateSourceFailure::DuplicateCandidateFile { + file: rel.clone(), + first_module: first_module.clone(), + conflicting_module: module_path.clone(), + }); + continue; + } + if let Some(first_file) = module_seen.get(module_path) { + failures.push(CandidateSourceFailure::DuplicateModuleIdentity { + module_path: module_path.clone(), + first_file: first_file.clone(), + conflicting_file: rel.clone(), + }); + continue; + } + file_seen.insert(rel.clone(), module_path.clone()); + module_seen.insert(module_path.clone(), rel.clone()); + let inputs = occurrence_binding_inputs_from_transport( + module_path.clone(), + transport.clone(), + grounding, + ); + universe.push(Rc::new(ParsedFileSupply { + file: rel.clone(), + row: cross_file_binding_closure_row( + transport.clone(), + inputs, + module_path.clone(), + rel, + ), + })); + } + } + } + } + + if !failures.is_empty() { + let first_failure = failures.remove(0); + return ReferenceDerivedClosureRun::UniverseRefused { + first_failure, + more_failures: failures, + }; + } + + let universe_size = universe.len(); + if !entry_seen { + return ReferenceDerivedClosureRun::EntryOutsideUniverse { + entry: entry_key, + universe: universe_size, + }; + } + + // THE DISCRIMINATING NUMBER, and it exists because two different defects produce the same word. + // A refusal reporting a name as unbound is EITHER "no file in the universe declares it" (the + // universe is short, and the loader is right) OR "declarations exist and candidate enumeration + // discarded them" (the filter is wrong, and the loader hides it behind the same word). Counting the + // declarations of that authored name across the universe's own parses separates the two in one + // measurement, instead of asking a reader to trust a hypothesis about which filter ran. + // + // Counted from the PRE-ASSEMBLY rows deliberately: the assembled transport is what the filter read, + // so a count taken there would inherit whatever the filter did and could never contradict it. + let count_declarations_named = |name: &str| -> (usize, Vec) { + let mut count = 0usize; + let mut files: Vec = Vec::new(); + for supply in &universe { + let mut here = 0usize; + for declaration in supply.row.transport.declarations.iter() { + for entry in supply.row.transport.index.entries.iter() { + if entry.projection.occurrence.value == declaration.occurrence.value + && entry.projection.authored_name == name + { + here += 1; + } + } + } + if here > 0 { + count += here; + if files.len() < 4 { + files.push(format!("{}x{}", supply.file, here)); + } + } + } + (count, files) + }; + + let outcome = + reference_derived_closure_fixed_point(entry_key.clone(), Rc::new(universe.clone().into())); + match &*outcome { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + admitted_files, + edges, + provenance, + iterations, + } => ReferenceDerivedClosureRun::Settled { + entry: entry_key, + universe: universe_size, + files: admitted_files.len(), + edges: edges.len(), + provenance: provenance.len(), + iterations: *iterations, + }, + ReferenceDerivedClosureFixedPoint::ClosureFixedPointRefused { + refusal, + iterations, + } => { + // The located arms render their LOCUS, not the population they came from. Printing the + // whole `BoundReferencePopulation` emits every failing occurrence's allocator ordinal, + // which is unreadable and says nothing about which authored name failed to bind. + let detail = match &**refusal { + crate::std_reference_derived_closure::ReferenceDerivedClosureRefusal::ClosureBindingRefused { + first_failure_locus, + failure_count, + .. + } => format!( + "ClosureBindingRefused: '{}' at bytes {}-{} did not bind ({} failing reference(s) in the selected population, all classes)", + first_failure_locus.authored_name, + first_failure_locus.diagnostic_span.start, + first_failure_locus.diagnostic_span.end, + failure_count + ), + other => format!("{other:?}"), + }; + // READ THE WHOLE LIST, NOT THE HEAD. vivid-bat-377 measured a sibling program drawing + // conclusions from a diagnostic head that was a constant prefix on every failure, so the + // head discriminated nothing and the real cause sat in the tail. This report had the same + // shape: every verdict below was taken from `first_failure` with `more_failures` unread. + // The head here is not a known constant, but "I only ever looked at the first one" is not a + // property worth betting a lane on, so the classes and names of the whole population are + // rendered. + let population_detail = match &**refusal { + crate::std_reference_derived_closure::ReferenceDerivedClosureRefusal::ClosureBindingRefused { + population, + .. + } => Some(summarize_population_failures(population)), + _ => None, + }; + let detail = match &**refusal { + crate::std_reference_derived_closure::ReferenceDerivedClosureRefusal::ClosureBindingRefused { + first_failure_locus, + .. + } => { + let (declared, sites) = + count_declarations_named(&first_failure_locus.authored_name); + format!( + "{detail} [universe declares '{}' {} time(s){}]", + first_failure_locus.authored_name, + declared, + if sites.is_empty() { + String::new() + } else { + format!(" in {}", sites.join(", ")) + } + ) + } + _ => detail, + }; + let detail = match population_detail { + Some(summary) => format!("{detail} population={summary}"), + None => detail, + }; + ReferenceDerivedClosureRun::Refused { + entry: entry_key, + universe: universe_size, + detail, + iterations: *iterations, + } + } + ReferenceDerivedClosureFixedPoint::ClosureFixedPointExhausted { + admitted_files, + iterations, + } => ReferenceDerivedClosureRun::Exhausted { + entry: entry_key, + universe: universe_size, + files: admitted_files.len(), + iterations: *iterations, + }, + } +} + fn collect_module_binding_manifest_rows(source_roots: &[String]) -> Vec { let root_variants: Vec = source_roots .iter() @@ -29376,37 +29769,34 @@ fn source_root_ref_token_for_path( } } -fn source_root_ingest_symbol_from_stem(stem: &str) -> String { - let mut body = String::new(); - for ch in stem.chars() { - if ch.is_ascii_alphanumeric() || ch == '_' { - body.push(ch); - } else { - body.push('_'); - } - } - if body.is_empty() { - body.push_str("host_sr_empty"); - } else if body.as_bytes()[0].is_ascii_digit() { - body = format!("sr_{body}"); - } - format!("^{body}") +/// Render an artifact identity as a `Symbol` from the repo-relative PATH, through the +/// string-taking intern authority. +/// +/// THE BASENAME IS NOT IN THE IDENTITY DECISION, and that is the repair rather than an +/// improvement to it. This used to mint a `^stem` symbol literal from the file's basename, +/// which is wrong twice. It is unrepresentable for any file whose stem is a `.dag` keyword — +/// `dag/std/import.dag`, `dag/extdeps/languages/go/module.dag` and +/// `dag/extdeps/bmc/capability.dag` minted `^import`, `^module` and `^capability`, so any +/// manifest scope touching `dag/` emitted a file that does not parse. And it is not an +/// identity at all: two files with the same basename in different directories minted the same +/// symbol, so the manifest's own rows collided silently. +/// +/// The path is already carried on the row as a string, so nothing new is observed here — only +/// the identity stops being derived from a lossy projection of it. `symbol_intern_lexeme` takes +/// a STRING (the same move `emit_module_binding_qualified_name` makes with +/// `qualified_name_from_string_segments`), so keywords are inert and no escape-hatch spelling of +/// `^` is needed. +fn source_root_ingest_symbol_for_path(path: &str) -> Result { + let escaped = dag_manifest_scalar_escape(path)?; + Ok(format!("symbol_intern_lexeme(lexeme: \"{escaped}\")")) } -pub fn source_root_ingest_artifact_id_for_path(path: &str) -> String { - let stem = Path::new(path) - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("host_sr"); - source_root_ingest_symbol_from_stem(stem) +pub fn source_root_ingest_artifact_id_for_path(path: &str) -> Result { + source_root_ingest_symbol_for_path(path) } -fn source_root_ingest_compilation_unit_for_path(path: &str) -> String { - let stem = Path::new(path) - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("host_sr"); - source_root_ingest_symbol_from_stem(stem) +fn source_root_ingest_compilation_unit_for_path(path: &str) -> Result { + source_root_ingest_symbol_for_path(path) } #[derive(Debug, Clone, PartialEq, Eq)] @@ -29703,8 +30093,8 @@ pub fn discover_source_root_reads_for_entry( } fn emit_source_root_read_witness(rec: &SourceRootReadRecord) -> Result { - let artifact_id = source_root_ingest_artifact_id_for_path(&rec.file_path); - let compilation_unit = source_root_ingest_compilation_unit_for_path(&rec.file_path); + let artifact_id = source_root_ingest_artifact_id_for_path(&rec.file_path)?; + let compilation_unit = source_root_ingest_compilation_unit_for_path(&rec.file_path)?; Ok(format!( "DagSourceReadWitness {{\n source: Medium {{ carried: \"{}\", fidelity: Lossless }},\n artifact: Artifact {{\n kind: SourceFile,\n id: {artifact_id},\n file_path: \"{}\"\n }},\n compilation_unit: {compilation_unit},\n source_root: {}\n}}", dag_embedded_dag_source_escape(&rec.source), @@ -29834,6 +30224,7 @@ pub fn emit_module_storage_binding_manifest( out.push_str("import v2.std.diagnostic { ByteRange, Textual }\n"); out.push_str("import v2.std.integer { Int }\n"); out.push_str("import v2.std.node { MintedOccurrence }\n"); + out.push_str("import v2.std.compilers.lexing { symbol_intern_lexeme }\n"); out.push_str("import v2.std.provenance { FromSource, span_index_empty, span_index_record }\n"); out.push_str("import v2.std.qualified_name { qualified_name_from_string_segments }\n"); out.push_str(&emit_module_binding_source_root_import(&rows)); @@ -29920,7 +30311,7 @@ fn emit_module_binding_span_index(span: &SourceSpan, file_symbol: &str) -> Strin fn emit_module_binding_row(row: &ModuleBindingManifestRow) -> Result { let qn = emit_module_binding_qualified_name(&row.module_path)?; - let artifact_id = source_root_ingest_artifact_id_for_path(&row.rel_path); + let artifact_id = source_root_ingest_artifact_id_for_path(&row.rel_path)?; let span_index = emit_module_binding_span_index(&row.ident_span, &artifact_id); Ok(format!( "module_storage_parsed_binding(\n module: {qn},\n artifact: Artifact {{\n kind: SourceFile,\n id: {artifact_id},\n file_path: \"{}\"\n }},\n span_index: {span_index},\n source_root: {}\n)", @@ -29972,6 +30363,7 @@ pub fn emit_source_root_ingest_manifest( out.push_str("import v2.std.artifact { Artifact, SourceFile }\n"); out.push_str("import v2.std.collection { List }\n"); out.push_str("import v2.std.text { String }\n"); + out.push_str("import v2.std.compilers.lexing { symbol_intern_lexeme }\n"); // Each DagSourceReadWitness carries a grounded `source_root: SourceRootRef` (V2Tree/DagTree, // #5473/#5486), so the manifest must import the constructors it references or every witness // fails with `undefined variable 'V2Tree'` (the source_root ingest gate's persistent RED). diff --git a/src/v1/stage0/src/lib.rs b/src/v1/stage0/src/lib.rs index ad0839647a7..8140b541885 100644 --- a/src/v1/stage0/src/lib.rs +++ b/src/v1/stage0/src/lib.rs @@ -80,6 +80,7 @@ pub mod std_pareto; pub mod std_process_termination; pub mod std_realization_schedule; pub mod std_reference_binding_observation; +pub mod std_reference_derived_closure; pub mod std_roster_frontier; pub mod std_serialization; pub mod std_source_annotation; @@ -140,11 +141,15 @@ pub mod v1_std_core; pub mod v1_test_non_ascii_perf_fixture; pub mod v1_tests_claim_caret_parse_smoke_test; pub mod v1_tests_claim_dag_parse_continuation_operator_witness_test; +pub mod v1_tests_claim_import_deletion_bridge_witness; pub mod v1_tests_claim_namespace_reference_derived_closure_production_admissions_witness_test; pub mod v1_tests_claim_occurrence_binding_parser_walk_witness_test; pub mod v1_tests_claim_occurrence_identity_debt_receipt_test; pub mod v1_tests_claim_ordinary_frontend_observation_test; +pub mod v1_tests_claim_parser_declaration_category_matrix_witness_test; +pub mod v1_tests_claim_parser_declaration_matrix_support; pub mod v1_tests_claim_pattern_binder_declaration_node_test; +pub mod v1_tests_claim_reference_derived_closure_witness_test; pub mod v1_tests_claim_required_expr_newline_continuation_test; pub mod v1_tests_claim_v1_annotation_binding_test; pub mod v1_tests_claim_v1_annotation_capture_test; diff --git a/src/v1/stage0/src/std_occurrence_binding_candidates.rs b/src/v1/stage0/src/std_occurrence_binding_candidates.rs index 23b8b8df77a..b7a9212891e 100644 --- a/src/v1/stage0/src/std_occurrence_binding_candidates.rs +++ b/src/v1/stage0/src/std_occurrence_binding_candidates.rs @@ -21,11 +21,15 @@ use self::ModulePathFileIndexRefusal::*; use self::OccurrenceCandidateIndexBuild::*; use self::OccurrenceModulePathIndexRefusal::*; use self::ReferenceBindingProjection::*; +use self::ReferenceDependencyAdmission::*; use self::ReferenceDerivedClauseEProduction::*; use self::ReferenceDerivedClauseEProductionRefusal::*; use self::ReferenceDerivedDependencyProjection::*; use self::ReferenceDerivedProviderFileProjection::*; use self::ReferenceDerivedProviderFilesForAssembledClosure::*; +use self::ReferencePathBuild::*; +use self::ReferencePathRefusal::*; +use self::ReferencePathResolution::*; use self::Section13PopulationLawEvidence::*; use self::Section13PopulationLawId::*; use self::Section13PopulationLawRosterVerdict::*; @@ -37,10 +41,12 @@ pub use crate::std_decl_ref::{DeclField, DeclarationRef}; pub use crate::std_dissolution::unbound_dissolution; pub use crate::std_dissolution::DissolutionCondition; use crate::std_dissolution::DissolutionCondition::*; -pub use crate::std_occurrence_binding::OccurrenceBindingResult; use crate::std_occurrence_binding::OccurrenceBindingResult::{ OccurrenceAmbiguous, OccurrenceBound, OccurrenceUnbound, }; +pub use crate::std_occurrence_binding::{ + AmbiguousBindingCandidates, BindingCandidate, OccurrenceBindingResult, +}; pub use crate::std_occurrence_binding_resolve::resolve_reference_occurrence_binding; pub use crate::std_occurrence_binding_resolve::OccurrenceReferenceBindingOutcome; use crate::std_occurrence_binding_resolve::OccurrenceReferenceBindingOutcome::{ @@ -50,6 +56,9 @@ use crate::std_occurrence_identity::OccurrenceCategory::TypeOccurrence; use crate::std_occurrence_identity::OccurrenceCategoryClauseEDependencyInducingVerdict::{ OccurrenceCategoryClauseEDependencyInducing, OccurrenceCategoryClauseEDependencyNotInducing, }; +use crate::std_occurrence_identity::OccurrenceCategoryModuleScopeExposureVerdict::{ + OccurrenceCategoryModuleScopeExposed, OccurrenceCategoryModuleScopeNotExposed, +}; use crate::std_occurrence_identity::OccurrenceTransportRefusal::UnknownOccurrenceIdentity; use crate::std_occurrence_identity::OccurrenceTransportValidation::{ OccurrenceTransportRefused, OccurrenceTransportValidated, @@ -57,19 +66,22 @@ use crate::std_occurrence_identity::OccurrenceTransportValidation::{ pub use crate::std_occurrence_identity::{ alloc_occurrence_id, authored_token_ordinal_before, authored_token_ordinal_eq, occurrence_category_clause_e_dependency_inducing_verdict, - occurrence_containment_path_is_prefix_of, occurrence_containment_paths_equal, - occurrence_id_allocator_initial, occurrence_transport_validate, + occurrence_category_module_scope_exposure_verdict, occurrence_containment_path_is_prefix_of, + occurrence_containment_paths_equal, occurrence_id_allocator_initial, + occurrence_transport_validate, }; pub use crate::std_occurrence_identity::{ AuthoredTokenOrdinal, DeclarationOccurrence, OccurrenceCategory, - OccurrenceCategoryClauseEDependencyInducingVerdict, OccurrenceContainmentPath, OccurrenceId, + OccurrenceCategoryClauseEDependencyInducingVerdict, + OccurrenceCategoryModuleScopeExposureVerdict, OccurrenceContainmentPath, OccurrenceId, OccurrenceIdAllocResult, OccurrenceIdAllocator, OccurrenceIndex, OccurrenceIndexEntry, OccurrenceProjection, OccurrenceTransport, OccurrenceTransportRefusal, - OccurrenceTransportValidation, ReferenceOccurrence, ValidatedOccurrenceTransport, + OccurrenceTransportValidation, ReferenceOccurrence, ReferencePath, ReferencePathSegment, + ValidatedOccurrenceTransport, }; pub use crate::std_roster_frontier::declaration_ref_eq; use crate::std_types::Bool::*; -pub use crate::std_types::{Bool, CommitSha, FilePath, List, NonEmptyStr}; +pub use crate::std_types::{Bool, CommitSha, FilePath, List, NonEmptyStr, SourceSpan}; use crate::v1_rt; use crate::v1_rt::{VecCompat, VecJoin}; use crate::NonEmptyBTreeSet; @@ -330,7 +342,7 @@ pub fn declaration_exposure_eq( pub fn declaration_exposure_from_containment_note() -> String { thread_local! { static CACHED: String = { - "Single authority for declaration exposure from containment prefix shape and module path. OccurrenceCategory is never consulted. DeclarationExposureGrounding selects how module-root and direct-module-child shapes map to DeclarationExposure: NamespaceStructuralRootExposure (N2 resolve path — empty ancestors => RootExposure); ModuleLocalMemberExposure (in-module binding walk — module-root and direct-module-child => ModuleExposure); CrossFileProviderExportedExposure (cross-file provider declarations exported to consumers — module-root and direct-module-child => RootExposure at derivation, never via a post-hoc upgrade). Nested declarations always derive LexicalExposure via occurrence_containment_parent_scope.".to_string() + "Single authority for declaration exposure from containment prefix shape and module path. OccurrenceCategory is never consulted. DeclarationExposureGrounding selects how module-root and direct-module-child shapes map to DeclarationExposure: NamespaceStructuralRootExposure (N2 resolve path — empty ancestors => RootExposure); ModuleLocalMemberExposure (every ordinary module member, in-file or cross-file — module-root and direct-module-child => ModuleExposure). Nested declarations always derive LexicalExposure via occurrence_containment_parent_scope. CrossFileProviderExportedExposure (module member => RootExposure for consumers in other files) was DELETED 2026-08-13: it made the whole source-root universe the visibility set, so any globally unique leaf bound bare from anywhere and the accepted declaration for one file changed with which file was the entry. A module member is exposed as a member and reached by projection (projected_reference_candidates_note); the universe is the declaration denominator, never the visibility set.".to_string() }; } CACHED.with(|c: &String| c.clone()) @@ -343,7 +355,6 @@ pub fn declaration_exposure_from_containment_note() -> String { pub enum DeclarationExposureGrounding { NamespaceStructuralRootExposure, ModuleLocalMemberExposure, - CrossFileProviderExportedExposure, } pub fn occurrence_containment_parent_scope( @@ -365,9 +376,30 @@ pub fn occurrence_containment_parent_scope( } } +pub fn declaration_exposure_for_nested( + module_path: String, + exposing_scope: Rc, + category: OccurrenceCategory, +) -> Rc { + match (*occurrence_category_module_scope_exposure_verdict(category.clone())).clone() { + OccurrenceCategoryModuleScopeExposureVerdict::OccurrenceCategoryModuleScopeExposed => { + Rc::new(DeclarationExposure::ModuleExposure { + module: module_path.clone(), + }) + } + OccurrenceCategoryModuleScopeExposureVerdict::OccurrenceCategoryModuleScopeNotExposed { + category: _, + .. + } => Rc::new(DeclarationExposure::LexicalExposure { + exposing_scope: exposing_scope.clone(), + }), + } +} + pub fn declaration_exposure_module_local_member( module_path: String, containment: Rc, + category: OccurrenceCategory, ) -> Rc { match occurrence_containment_parent_scope(containment.ancestors.clone()) { None => Rc::new(DeclarationExposure::ModuleExposure { @@ -380,9 +412,11 @@ pub fn declaration_exposure_module_local_member( module: module_path.clone(), }) } else { - Rc::new(DeclarationExposure::LexicalExposure { - exposing_scope: exposing_scope.clone(), - }) + declaration_exposure_for_nested( + module_path.clone(), + exposing_scope.clone(), + category.clone(), + ) } } } @@ -391,6 +425,7 @@ pub fn declaration_exposure_module_local_member( pub fn declaration_exposure_namespace_structural_root( module_path: String, containment: Rc, + category: OccurrenceCategory, ) -> Rc { match occurrence_containment_parent_scope(containment.ancestors.clone()) { None => Rc::new(DeclarationExposure::RootExposure), @@ -401,9 +436,11 @@ pub fn declaration_exposure_namespace_structural_root( module: module_path.clone(), }) } else { - Rc::new(DeclarationExposure::LexicalExposure { - exposing_scope: exposing_scope.clone(), - }) + declaration_exposure_for_nested( + module_path.clone(), + exposing_scope.clone(), + category.clone(), + ) } } } @@ -413,32 +450,22 @@ pub fn declaration_exposure_from_containment( module_path: String, containment: Rc, grounding: DeclarationExposureGrounding, + category: OccurrenceCategory, ) -> Rc { match grounding.clone() { DeclarationExposureGrounding::NamespaceStructuralRootExposure => { - declaration_exposure_namespace_structural_root(module_path.clone(), containment.clone()) + declaration_exposure_namespace_structural_root( + module_path.clone(), + containment.clone(), + category.clone(), + ) } DeclarationExposureGrounding::ModuleLocalMemberExposure => { - declaration_exposure_module_local_member(module_path.clone(), containment.clone()) - } - DeclarationExposureGrounding::CrossFileProviderExportedExposure => { - match (*declaration_exposure_module_local_member( + declaration_exposure_module_local_member( module_path.clone(), containment.clone(), - )) - .clone() - { - DeclarationExposure::ModuleExposure { module: _, .. } => { - Rc::new(DeclarationExposure::RootExposure) - } - DeclarationExposure::LexicalExposure { - exposing_scope: exposing_scope, - .. - } => Rc::new(DeclarationExposure::LexicalExposure { - exposing_scope: exposing_scope.clone(), - }), - DeclarationExposure::RootExposure => Rc::new(DeclarationExposure::RootExposure), - } + category.clone(), + ) } } } @@ -826,6 +853,77 @@ pub fn declaration_exposed_on_reference_chain( } } +pub fn head_reference_names_a_module_note() -> String { + thread_local! { + static CACHED: String = { + "THE HEAD OF A PROJECTION IS A MODULE, and at this grain the parser cannot say so. An expression path p.first arrives as a bare reference p plus a field projection, not as one dotted occurrence the way a TYPE path does, because the dot IS containment projection and the expression form is the structurally faithful one. So the head arrives spelled as an ordinary value reference (ExprVar, LexicalValueOccurrence) while the thing it names is a module declaration (NamespaceSegmentOccurrence), which is why the value-reference arm admits that pair: the reference site genuinely does not know whether a bare name is a local binder, a module member or a module, and the reference site is not where that is decided. A module declaration is supplied to a bare reference WITHOUT the on-chain exposure filter, deliberately: module-sibling exposure gates on same-module plus source order, both of which are within-file relations, and a module name is visible across files by design. Cardinality still decides -- a name that is both a local binder and a module segment supplies both and folds to Ambiguous. DISSOLVE-ON: one ReferencePath carrier {head, projections} that both syntactic positions feed, at which point the parser mints the head as a namespace reference directly and this category admission stops being needed. WHAT THIS DOES NOT DO: it binds the HEAD, which is what a file DEPENDENCY needs -- the provider file follows from the module declaration's own file. It does not descend to the member, so a bound head with a missing or ambiguous member is not yet distinguished here; that belongs to the resolver cutover, not to dependency derivation.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn projected_reference_candidates_note() -> String { + thread_local! { + static CACHED: String = { + "A reference whose authored text carries a qualifier (live_tree.LiveTreeDisposition) is ONE occurrence whose authored_name is the whole dotted path — the parser already mints it that way, so nothing new is minted here; what this adds is the resolution rule the flat declarations_by_name lookup never had. The leaf segment selects the same-spelled bucket; the qualifier selects which declaring module may answer, by segment-suffix over the declaration's own OccurrenceModulePathRow (tmp.q.b is named by b or by q.b or in full). Exposure for a projected reference is ModuleExposure for exactly that module and NOTHING else: RootExposure does not answer a qualified name (a root declaration is not a member of the named module) and the module-sibling ordinal gate is deliberately NOT applied, because source order is a within-file relation and two files have no authored order between them. Cardinality stays downstream: if two modules end in the same segment both declarations are supplied and the population folds to Ambiguous, which is the correct refusal rather than a nearest-match heuristic (DESIGN section 4 — a heuristic is never necessary in a closed system).".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn reference_qualifier_of_authored_name(name: String) -> Option { + match v1_rt::string_contains(&name, ".".to_string()) { + false => None, + true => match Rc::new( + name.clone() + .split(&".".to_string()) + .map(|s| s.to_string()) + .collect::>(), + ) + .last() + .cloned() + { + None => None, + Some(leaf) => Some(v1_rt::substring( + &name, + 0, + ((v1_rt::string_length(&name) - v1_rt::string_length(&leaf)) - 1), + )), + }, + } +} + +pub fn module_named_by_qualifier(module: String, qualifier: String) -> bool { + ((module.clone() == qualifier.clone()) + || v1_rt::ends_with( + module.clone(), + v1_rt::concat(".".to_string(), qualifier.clone()), + )) +} + +pub fn declaration_projected_from_named_module( + declaration: Rc, + qualifier: String, + exposure_by_occurrence: Rc>>, +) -> bool { + match exposure_of_occurrence( + exposure_by_occurrence.clone(), + declaration.occurrence.clone(), + ) { + None => false, + Some(exposure) => match (*exposure.clone()).clone() { + DeclarationExposure::ModuleExposure { + module: exposure_module, + .. + } => module_named_by_qualifier(exposure_module.clone(), qualifier.clone()), + DeclarationExposure::LexicalExposure { + exposing_scope: _, .. + } => false, + DeclarationExposure::RootExposure => false, + }, + } +} + pub fn occurrence_candidate_index_note() -> String { thread_local! { static CACHED: String = { @@ -839,6 +937,7 @@ pub fn occurrence_candidate_index_note() -> String { pub struct OccurrenceCandidateIndex { pub entries_by_id: Rc>>, pub declarations_by_name: Rc>>>>, + pub module_declarations_by_segment: Rc>>>>, pub module_by_occurrence: Rc>, pub exposure_by_occurrence: Rc>>, pub order_by_occurrence: Rc>, @@ -947,6 +1046,76 @@ pub fn declarations_by_name_build( } } +pub fn module_declarations_by_segment_note() -> String { + thread_local! { + static CACHED: String = { + "A module declaration carries its WHOLE dotted path as one authored name (v1.compiler.parse parse_module stamps the module node once, name: mod_name), so a head reference spelled with the last segment alone -- the p in p.first -- finds nothing in declarations_by_name, which is keyed by authored spelling. This index buckets module declarations by their last segment so a head reference has something to resolve against, and it is keyed by segment rather than by full path because the ratified migration form is the SHORTEST qualifying container path, not the path from the root. Two modules ending in the same segment therefore supply two candidates and fold to Ambiguous downstream -- the refusal, never a nearest-match. Only NamespaceSegmentOccurrence declarations enter: the category is read from the DeclarationOccurrence rather than inferred from the presence of a dot in the text, so an ordinary declaration whose name happened to contain a dot could not impersonate a module.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn module_declaration_last_segment(authored_name: String) -> String { + match Rc::new( + authored_name + .clone() + .split(&".".to_string()) + .map(|s| s.to_string()) + .collect::>(), + ) + .last() + .cloned() + { + None => authored_name.clone(), + Some(segment) => segment.clone(), + } +} + +pub fn module_declarations_by_segment_step( + acc: Rc>>>>, + entries_by_id: Rc>>, + declaration: Rc, +) -> Rc>>>> { + match declaration.category.clone() { + OccurrenceCategory::NamespaceSegmentOccurrence => { + match v1_rt::map_get(&entries_by_id, declaration.occurrence.clone().value.clone()) { + None => acc.clone(), + Some(entry) => { + let segment = module_declaration_last_segment( + entry.projection.clone().authored_name.clone(), + ); + match v1_rt::map_get(&acc, segment.clone()) { + None => v1_rt::rc_map_insert( + acc.clone(), + segment.clone(), + Rc::new(vec![declaration.clone()]), + ), + Some(existing) => v1_rt::rc_map_insert( + acc.clone(), + segment.clone(), + v1_rt::concat(Rc::new(vec![declaration.clone()]), existing.clone()), + ), + } + } + } + } + _ => acc.clone(), + } +} + +pub fn module_declarations_by_segment_build( + entries_by_id: Rc>>, + declarations: Rc>>, +) -> Rc>>>> { + declarations.clone().iter().cloned().fold( + v1_rt::rc_empty_map::>>>(), + |acc: Rc>>>>, + declaration: Rc| { + module_declarations_by_segment_step(acc, entries_by_id.clone(), declaration.clone()) + }, + ) +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(tag = "_variant")] pub enum OccurrenceCandidateIndexBuild { @@ -1022,6 +1191,7 @@ pub fn occurrence_candidate_index_build( index: Rc::new(OccurrenceCandidateIndex { entries_by_id: validated.entries_by_id.clone(), declarations_by_name: by_name.clone(), + module_declarations_by_segment: module_declarations_by_segment_build(validated.entries_by_id.clone(), validated.declarations.clone()), module_by_occurrence: module_path_build.module_by_occurrence.clone(), exposure_by_occurrence: exposure_build.exposure_by_occurrence.clone(), order_by_occurrence: order_build.order_by_occurrence.clone(), @@ -1056,35 +1226,89 @@ pub fn candidate_occurrence_ids_for_reference( reference.occurrence.clone().value.clone(), ) { None => Rc::new(vec![]), - Some(entry) => match v1_rt::map_get( - &index.declarations_by_name.clone(), + Some(entry) => match reference_qualifier_of_authored_name( entry.projection.clone().authored_name.clone(), ) { - None => Rc::new(vec![]), - Some(same_spelling) => Rc::new({ - let mut __result = Vec::new(); - for declaration in Rc::new({ - let mut __result = Vec::new(); - for declaration in v1_rt::reverse(same_spelling.clone()).iter().cloned() { - if declaration_exposed_on_reference_chain( - declaration.clone(), - reference.clone(), - index.module_by_occurrence.clone(), - index.exposure_by_occurrence.clone(), - index.order_by_occurrence.clone(), - ) { - __result.push(declaration); + None => { + let on_chain = match v1_rt::map_get( + &index.declarations_by_name.clone(), + entry.projection.clone().authored_name.clone(), + ) { + None => Rc::new(vec![]), + Some(same_spelling) => Rc::new({ + let mut __result = Vec::new(); + for declaration in Rc::new({ + let mut __result = Vec::new(); + for declaration in v1_rt::reverse(same_spelling.clone()).iter().cloned() + { + if declaration_exposed_on_reference_chain( + declaration.clone(), + reference.clone(), + index.module_by_occurrence.clone(), + index.exposure_by_occurrence.clone(), + index.order_by_occurrence.clone(), + ) { + __result.push(declaration); + } + } + __result + }) + .iter() + .cloned() + { + __result.push(declaration.occurrence.clone()); } - } - __result - }) - .iter() - .cloned() - { - __result.push(declaration.occurrence.clone()); + __result + }), + }; + let named_modules = match v1_rt::map_get( + &index.module_declarations_by_segment.clone(), + entry.projection.clone().authored_name.clone(), + ) { + None => Rc::new(vec![]), + Some(modules) => Rc::new({ + let mut __result = Vec::new(); + for declaration in v1_rt::reverse(modules.clone()).iter().cloned() { + __result.push(declaration.occurrence.clone()); + } + __result + }), + }; + v1_rt::concat(on_chain.clone(), named_modules.clone()) + } + Some(qualifier) => { + let leaf = v1_rt::substring( + &entry.projection.clone().authored_name.clone(), + (v1_rt::string_length(&qualifier) + 1), + v1_rt::string_length(&entry.projection.clone().authored_name.clone()), + ); + match v1_rt::map_get(&index.declarations_by_name.clone(), leaf.clone()) { + None => Rc::new(vec![]), + Some(same_spelling) => Rc::new({ + let mut __result = Vec::new(); + for declaration in Rc::new({ + let mut __result = Vec::new(); + for declaration in v1_rt::reverse(same_spelling.clone()).iter().cloned() + { + if declaration_projected_from_named_module( + declaration.clone(), + qualifier.clone(), + index.exposure_by_occurrence.clone(), + ) { + __result.push(declaration); + } + } + __result + }) + .iter() + .cloned() + { + __result.push(declaration.occurrence.clone()); + } + __result + }), } - __result - }), + } }, } } @@ -1271,23 +1495,23 @@ pub fn bound_reference_population_fold_step( }), } } - ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { - occurrence: _, .. - } => match acc.first_failure.clone() { - None => Rc::new(BoundReferencePopulationBuild { - providers_reversed: acc.providers_reversed.clone(), - first_failure: Some(projection.clone()), - more_failures_reversed: acc.more_failures_reversed.clone(), - }), - Some(_) => Rc::new(BoundReferencePopulationBuild { - providers_reversed: acc.providers_reversed.clone(), - first_failure: acc.first_failure.clone(), - more_failures_reversed: v1_rt::concat( - Rc::new(vec![projection.clone()]), - acc.more_failures_reversed.clone(), - ), - }), - }, + ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { .. } => { + match acc.first_failure.clone() { + None => Rc::new(BoundReferencePopulationBuild { + providers_reversed: acc.providers_reversed.clone(), + first_failure: Some(projection.clone()), + more_failures_reversed: acc.more_failures_reversed.clone(), + }), + Some(_) => Rc::new(BoundReferencePopulationBuild { + providers_reversed: acc.providers_reversed.clone(), + first_failure: acc.first_failure.clone(), + more_failures_reversed: v1_rt::concat( + Rc::new(vec![projection.clone()]), + acc.more_failures_reversed.clone(), + ), + }), + } + } ReferenceBindingProjection::ReferenceBindingProjectionTransportRefused { refusal: _, .. @@ -2076,64 +2300,1090 @@ pub fn reference_derived_dependency_projection( } } -pub fn reference_derived_dependency_binding_reference_filter_note() -> String { +pub fn reference_dependency_admission_note() -> String { thread_local! { static CACHED: String = { - "Clause-(e) structural walks bind only reference categories that occurrence_category_clause_e_dependency_inducing_verdict classifies as OccurrenceCategoryClauseEDependencyInducing. TypeOccurrence references are N2's resolve_type_reference_containment_binding lane — parser-produced return-type annotations must not refuse the whole population when Int is not module-declared. Parser witnesses that need per-name resolution already avoid whole-transport walks for this reason; production assembled-closure projection applies the same boundary here.".to_string() + "THE DEPENDENCY-INDUCING QUESTION IS BROADER THAN CLAUSE (e), and this authority composes the two rather than widening either.\n\nClause (e) is one clause of one contract, and its verdict is deliberately left alone: TypeOccurrence is still OccurrenceCategoryClauseEDependencyNotInducing, because reclassifying it there to reuse the filter would make the clause say something its own contract does not, and every consumer of that clause would silently inherit the change. What is true instead is that a type reference induces a file dependency by a DIFFERENT route -- N2's typed entry, whose category guard is part of its contract -- so the admission surface has one arm per route and an excluded arm that carries the category it excluded.\n\nComposition, not a second table: this reads the clause-(e) verdict first and only inspects the category inside its NotInducing arm. A parallel match over the seven categories would be a second classification of the same coproduct, so clause (e) remains the authority for its own question and this authority answers only what it adds.\n\nWHY THE PRODUCTION PROJECTION DOES NOT YET USE IT, stated because a surface with no consumer is exactly what this repository calls coverage by illusion. Admitting type references makes a parser-produced annotation such as `Int` enter the population, and its declaration is not in an assembled closure that was never given std.types -- so the population refuses, WHOLE, by the totality law. That refusal is correct and is the point: it is the demand the loader has to satisfy, named at the exact occurrence, and it is the loader work list rather than a skip. Silently dropping an unbindable type reference to keep the projection green would be the empty-observation narrow -- rendering `could not see the provider` as `nothing is depended on` -- which is strictly worse than the widen DESIGN section 5 already forbids.\n\nSo this lands as the joined walk plus its witnesses, and the production switch at reference_derived_dependency_projection_for_assembled_closure happens when the loader can supply the providers the demand names. DISSOLVE-ON: that switch, at which point reference_derived_dependency_binding_references has no caller and deletes.".to_string() }; } CACHED.with(|c: &String| c.clone()) } -pub fn reference_derived_dependency_binding_references( - references: Rc>>, -) -> Rc>> { - { - let build = references.clone().iter().cloned().fold(Rc::new(ReferenceDerivedDependencyBindingReferenceBuild { - references_reversed: Rc::new(vec![]), -}), |acc: Rc, reference: Rc| match (*occurrence_category_clause_e_dependency_inducing_verdict(reference.category.clone())).clone() { - OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyInducing => Rc::new(ReferenceDerivedDependencyBindingReferenceBuild { - references_reversed: v1_rt::concat(Rc::new(vec![reference.clone()]), acc.references_reversed.clone()), -}), - OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyNotInducing { category: _, .. } => acc.clone(), -}); - v1_rt::reverse(build.references_reversed.clone()) +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ReferenceDependencyAdmission { + ReferenceDependencyAdmittedClauseE, + ReferenceDependencyAdmittedTypeReference, + ReferenceDependencyExcluded { category: OccurrenceCategory }, +} +impl ReferenceDependencyAdmission { + pub fn category(&self) -> OccurrenceCategory { + match self { + ReferenceDependencyAdmission::ReferenceDependencyAdmittedClauseE => { + panic!("no category on unit variant") + } + ReferenceDependencyAdmission::ReferenceDependencyAdmittedTypeReference => { + panic!("no category on unit variant") + } + ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: __val, .. + } => __val.clone(), + } + } +} + +pub fn module_member_projection_note() -> String { + thread_local! { + static CACHED: String = { + "THE HEAD-ONLY WIDEN, CLOSED. An expression path p.first is a FieldOccurrence reference whose base is its child, and FieldOccurrence is excluded from clause-E dependency inducement -- correctly, for a record field access. The exclusion was applied to the DEPENDENCY question and silently answered the BINDING question too: an excluded reference is never added to the required population, so it never had to resolve. Measured consequence, reproduced before the repair: p.no_such_member settled with one edge against a provider that declares no such member, because the edge came from the head binding to the module while the member was never asked about. A projection off a module head is not a record field access, and the difference is decidable here rather than at the category: the base resolves to a NamespaceSegmentOccurrence or it does not. When it does, the segment must resolve among that module's own members, and the dependency is attributed to the TERMINAL declaration -- which is what a file dependency actually rests on -- rather than to the module head that merely names where to look.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn reference_path_producer_note() -> String { + thread_local! { + static CACHED: String = { + "TWO SPELLINGS IN, ONE PATH OUT. The dotted spelling is decided first because it is decidable from the authored text alone: a name carrying a dot is one occurrence covering the whole path, so its head is the first segment and every later segment inherits the one span the parser allocated -- coarse, and honestly so, because there is no second occurrence to carry a narrower one. Otherwise the reference is walked DOWN its containment children: a projection segment has exactly one child (its receiver), so more than one child is not a longer path but a shape this producer does not model, and it refuses rather than picking one. A reference with no child and no dot is a bare name, which is a path of zero segments -- the ordinary case, not a special one. THE ORDER: walking down yields segments outermost-first, so the accumulator is built head-adjacent-last and reversed once at the end, never appended per step.".to_string() + }; } + CACHED.with(|c: &String| c.clone()) } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct ReferenceDerivedDependencyBindingReferenceBuild { - pub references_reversed: Rc>>, +#[serde(tag = "_variant")] +pub enum ReferencePathBuild { + ReferencePathBuilt { path: Rc }, + ReferencePathRefused { reason: Rc }, } -pub fn structural_binding_walk_selected_references( - transport: Rc, - inputs: Rc, +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ReferencePathRefusal { + ReferencePathOccurrenceNotIndexed { + occurrence: OccurrenceId, + }, + ReferencePathBranchingProjection { + occurrence: OccurrenceId, + child_count: i64, + }, + ReferencePathEmptySegmentText { + occurrence: OccurrenceId, + }, + ReferencePathDescentUnbounded { + occurrence: OccurrenceId, + }, +} +impl ReferencePathRefusal { + pub fn occurrence(&self) -> OccurrenceId { + match self { + ReferencePathRefusal::ReferencePathOccurrenceNotIndexed { + occurrence: __val, .. + } => __val.clone(), + ReferencePathRefusal::ReferencePathBranchingProjection { + occurrence: __val, .. + } => __val.clone(), + ReferencePathRefusal::ReferencePathEmptySegmentText { + occurrence: __val, .. + } => __val.clone(), + ReferencePathRefusal::ReferencePathDescentUnbounded { + occurrence: __val, .. + } => __val.clone(), + } + } +} + +pub fn reference_list_count(xs: Rc>>) -> i64 { + (xs.clone().len() as i64) +} + +pub fn reference_children( references: Rc>>, -) -> Rc { - match (*occurrence_candidate_index_build(transport.clone(), inputs.clone())).clone() { - OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexTransportRefused { - refusal: refusal, - .. - } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { - refusal: Rc::new( - StructuralBindingIndexRefusal::StructuralBindingTransportRefusal { - refusal: refusal.clone(), - }, - ), - }), - OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexModulePathRefused { - refusal: refusal, - .. - } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { - refusal: Rc::new( - StructuralBindingIndexRefusal::StructuralBindingModulePathRefusal { - refusal: refusal.clone(), - }, - ), - }), - OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexExposureRefused { - refusal: refusal, + reference: Rc, +) -> Rc>> { + v1_rt::reverse(references.clone().iter().cloned().fold( + Rc::new(vec![]), + |acc: _, candidate: Rc| { + let __fm = v1_rt::reverse(candidate.containment.clone().ancestors.clone()); + if __fm.is_empty() { + acc.clone() + } else { + let parent = (*__fm)[0].clone(); + match (parent.value.clone() == reference.occurrence.clone().value.clone()) { + true => Rc::new({ + let mut __cons_v = (*acc.clone()).clone(); + __cons_v.insert(0, candidate.clone()); + __cons_v + }), + false => acc.clone(), + } + } + }, + )) +} + +pub fn reference_authored_name( + index: Rc, + reference: Rc, +) -> Option { + match v1_rt::map_get( + &index.entries_by_id.clone(), + reference.occurrence.clone().value.clone(), + ) { + None => None, + Some(entry) => Some(entry.projection.clone().authored_name.clone()), + } +} + +pub fn reference_path_segments_from_dotted( + name: String, + span: Rc, +) -> Rc>> { + Rc::new( + name.clone() + .split(&".".to_string()) + .map(|s| s.to_string()) + .collect::>(), + ) + .iter() + .cloned() + .fold(Rc::new(vec![]), |acc: _, segment: String| { + Rc::new({ + let mut __cons_v = (*acc).clone(); + __cons_v.insert( + 0, + Rc::new(ReferencePathSegment { + name: segment.clone(), + diagnostic_span: span.clone(), + occurrence: None, + }), + ); + __cons_v + }) + }) +} + +pub fn reference_path_module_prefix_note() -> String { + thread_local! { + static CACHED: String = { + "THE HEAD OF A DOTTED PATH IS A MODULE PATH, NOT A SEGMENT, and an earlier revision of this producer got that wrong in a way worth recording because it looked correct against the corpus. It split at EVERY dot and took the FIRST segment as the head, which happens to work for the import-era spelling live_tree.LiveTreeDisposition -- one qualifier segment, one member -- and fails for every other shape. A fully qualified std.types.NonEmptyStr yields head std, which names no module, so it refused at the head; and a three-segment live_tree.LiveTreeDisposition.ReadsLiveTree refused the same way, which would have read as multi-segment descent being unimplemented when the real defect was the split. The rule is instead the LONGEST PREFIX that names a module: segments are consumed into the head while they still name one, and everything after is descent. That serves both spellings from one rule -- the exact-path match answers a fully qualified reference and the suffix match answers a shortest-qualifier one, because module_named_by_qualifier already admits both -- so the qualification policy is a corpus-migration question rather than a producer question, which is the opposite of what a one-segment split implied.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn module_declaration_named_by_qualifier( + index: Rc, + declaration: Rc, + qualifier: String, +) -> bool { + match v1_rt::map_get( + &index.entries_by_id.clone(), + declaration.occurrence.clone().value.clone(), + ) { + None => false, + Some(entry) => { + ((entry.projection.clone().authored_name.clone() == qualifier.clone()) + || v1_rt::ends_with( + entry.projection.clone().authored_name.clone(), + v1_rt::concat(".".to_string(), qualifier.clone()), + )) + } + } +} + +pub fn module_declared_by_qualifier( + index: Rc, + qualifier: String, +) -> bool { + match v1_rt::map_get( + &index.module_declarations_by_segment.clone(), + module_declaration_last_segment(qualifier.clone()), + ) { + None => false, + Some(modules) => modules.clone().iter().cloned().fold( + false, + |found: bool, declaration: Rc| { + (found + || module_declaration_named_by_qualifier( + index.clone(), + declaration.clone(), + qualifier.clone(), + )) + }, + ), + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ModulePrefixScan { + pub taken: i64, + pub prefix: String, + pub best: i64, +} + +pub fn module_prefix_scan_step( + index: Rc, + acc: Rc, + segment: String, +) -> Rc { + { + let grown = prefix_append(acc.prefix.clone(), acc.taken.clone(), segment.clone()); + let taken = (acc.taken.clone() + 1); + match module_declared_by_qualifier(index.clone(), grown.clone()) { + true => Rc::new(ModulePrefixScan { + taken: taken.clone(), + prefix: grown.clone(), + best: taken.clone(), + }), + false => Rc::new(ModulePrefixScan { + taken: taken.clone(), + prefix: grown.clone(), + best: acc.best.clone(), + }), + } + } +} + +pub fn module_prefix_match_length( + index: Rc, + segments: Rc>, +) -> i64 { + segments + .clone() + .iter() + .cloned() + .fold( + Rc::new(ModulePrefixScan { + taken: 0, + prefix: "".to_string(), + best: 0, + }), + |acc: Rc, segment: String| { + module_prefix_scan_step(index.clone(), acc, segment.clone()) + }, + ) + .best + .clone() +} + +pub fn joined_prefix_step(acc: Rc, segment: String) -> Rc { + match (acc.taken.clone() >= acc.best.clone()) { + true => Rc::new(ModulePrefixScan { + taken: (acc.taken.clone() + 1), + prefix: acc.prefix.clone(), + best: acc.best.clone(), + }), + false => Rc::new(ModulePrefixScan { + taken: (acc.taken.clone() + 1), + prefix: prefix_append(acc.prefix.clone(), acc.taken.clone(), segment.clone()), + best: acc.best.clone(), + }), + } +} + +pub fn prefix_append(prefix: String, taken: i64, segment: String) -> String { + match (taken.clone() == 0) { + true => segment, + false => v1_rt::concat(prefix.clone(), v1_rt::concat(".".to_string(), segment)), + } +} + +pub fn joined_prefix(segments: Rc>, count: i64) -> String { + segments + .clone() + .iter() + .cloned() + .fold( + Rc::new(ModulePrefixScan { + taken: 0, + prefix: "".to_string(), + best: count.clone(), + }), + joined_prefix_step, + ) + .prefix + .clone() +} + +pub fn reference_path_segment_of(name: String, span: Rc) -> Rc { + Rc::new(ReferencePathSegment { + name: name.clone(), + diagnostic_span: span.clone(), + occurrence: None, + }) +} + +pub fn reference_path_tail_segments( + segments: Rc>, + head_len: i64, + span: Rc, +) -> Rc>> { + Rc::new({ + let mut __result = Vec::new(); + for segment in Rc::new( + segments + .clone() + .iter() + .cloned() + .skip(head_len.clone() as usize) + .collect::>(), + ) + .iter() + .cloned() + { + __result.push(reference_path_segment_of(segment.clone(), span.clone())); + } + __result + }) +} + +pub fn reference_path_with_module_head( + reference: Rc, + segments: Rc>, + head_len: i64, +) -> Rc { + Rc::new(ReferencePathBuild::ReferencePathBuilt { + path: Rc::new(ReferencePath { + head_name: joined_prefix(segments.clone(), head_len.clone()), + head_span: reference.diagnostic_span.clone(), + head_occurrence: reference.occurrence.clone(), + segments: reference_path_tail_segments( + segments.clone(), + head_len.clone(), + reference.diagnostic_span.clone(), + ), + }), + }) +} + +pub fn reference_path_with_first_segment_head( + reference: Rc, + segments: Rc>, +) -> Rc { + { + let __fm = segments.clone(); + if __fm.is_empty() { + Rc::new(ReferencePathBuild::ReferencePathRefused { + reason: Rc::new(ReferencePathRefusal::ReferencePathEmptySegmentText { + occurrence: reference.occurrence.clone(), + }), + }) + } else { + let first_segment = (*__fm)[0].clone(); + reference_path_with_module_head(reference.clone(), segments.clone(), 1) + } + } +} + +pub fn reference_path_from_dotted_name( + index: Rc, + reference: Rc, + name: String, +) -> Rc { + { + let segments = Rc::new( + name.clone() + .split(&".".to_string()) + .map(|s| s.to_string()) + .collect::>(), + ); + let head_len = module_prefix_match_length(index.clone(), segments.clone()); + match (head_len.clone() == 0) { + true => reference_path_with_first_segment_head(reference.clone(), segments.clone()), + false => reference_path_with_module_head( + reference.clone(), + segments.clone(), + head_len.clone(), + ), + } + } +} + +pub fn reference_path_descend( + mut index: Rc, + mut references: Rc>>, + mut reference: Rc, + mut segments: Rc>>, + mut fuel: i64, +) -> Rc { + loop { + match reference_authored_name(index.clone(), reference.clone()) { + None => { + break Rc::new(ReferencePathBuild::ReferencePathRefused { + reason: Rc::new(ReferencePathRefusal::ReferencePathOccurrenceNotIndexed { + occurrence: reference.occurrence.clone(), + }), + }); + } + Some(name) => { + let __fm = reference_children(references.clone(), reference.clone()); + if __fm.is_empty() { + break Rc::new(ReferencePathBuild::ReferencePathBuilt { + path: Rc::new(ReferencePath { + head_name: name.clone(), + head_span: reference.diagnostic_span.clone(), + head_occurrence: reference.occurrence.clone(), + segments: v1_rt::reverse(segments.clone()), + }), + }); + } else { + let child = (*__fm)[0].clone(); + let siblings: Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); + { + let __fm = siblings.clone(); + if __fm.is_empty() { + match (fuel.clone() <= 0) { + true => { + break Rc::new(ReferencePathBuild::ReferencePathRefused { + reason: Rc::new( + ReferencePathRefusal::ReferencePathDescentUnbounded { + occurrence: reference.occurrence.clone(), + }, + ), + }); + } + false => { + let __tco_0 = child.clone(); + let __tco_1 = Rc::new({ + let mut __cons_v = (*segments).clone(); + __cons_v.insert( + 0, + Rc::new(ReferencePathSegment { + name: name.clone(), + diagnostic_span: reference.diagnostic_span.clone(), + occurrence: Some(reference.occurrence.clone()), + }), + ); + __cons_v + }); + let __tco_2 = (fuel - 1); + reference = __tco_0; + segments = __tco_1; + fuel = __tco_2; + continue; + } + } + } else { + break Rc::new(ReferencePathBuild::ReferencePathRefused { + reason: Rc::new( + ReferencePathRefusal::ReferencePathBranchingProjection { + occurrence: reference.occurrence.clone(), + child_count: (1 + reference_list_count(siblings.clone())), + }, + ), + }); + } + } + } + } + } + } +} + +pub fn reference_path_of( + index: Rc, + references: Rc>>, + reference: Rc, +) -> Rc { + match reference_authored_name(index.clone(), reference.clone()) { + None => Rc::new(ReferencePathBuild::ReferencePathRefused { + reason: Rc::new(ReferencePathRefusal::ReferencePathOccurrenceNotIndexed { + occurrence: reference.occurrence.clone(), + }), + }), + Some(name) => match v1_rt::string_contains(&name, ".".to_string()) { + true => reference_path_from_dotted_name(index.clone(), reference.clone(), name.clone()), + false => reference_path_descend( + index.clone(), + references.clone(), + reference.clone(), + Rc::new(vec![]), + reference_list_count(references.clone()), + ), + }, + } +} + +pub fn reference_path_not_a_namespace_projection_note() -> String { + thread_local! { + static CACHED: String = { + "A DOT IS NOT ALWAYS A NAMESPACE STEP, and the difference is a THIRD STATE rather than a refusal. x.field on an ordinary record binder is a field access: it induces no cross-file dependency of its own (the dependency was already induced by the reference to x's TYPE), and its member is a field of a type rather than a member of a module. Answering it with SegmentUnbound would be the fabricated-plausible-output failure pointed at a well-formed program -- a correct field access reported as an unresolved name. Answering it with a resolved path would be worse, since it would attribute a dependency to whatever declaration happened to match the field spelling. So the head's own category decides: only a NamespaceSegmentOccurrence head descends, and any other head with segments present returns NotANamespaceProjection carrying the head declaration, which tells the caller this reference is somebody else's question. It is deliberately NOT an error arm and deliberately NOT success: the caller must handle three outcomes, because there are three.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn reference_path_resolution_note() -> String { + thread_local! { + static CACHED: String = { + "ONE HEAD BINDING, THEN CONTAINMENT. The head is resolved exactly once, through the same on-chain-plus-named-modules candidate rule an ordinary bare reference uses -- there is no second, segment-index reconstruction of the head beside it, because two derivations of one fact are two chances to disagree and the disagreement would be silent. Every later segment is a CONTAINMENT CHILD step: the declarations whose containment parent is the previous accepted declaration and whose authored name is the segment. That step is uniform for the first segment and the tenth, and it needed no qualifier suffix-match, because a module member's containment parent is measurably the module declaration occurrence itself. THE CARDINALITY RULE IS THE SAME AT EVERY STEP AND IS NOT SPECIAL-CASED: zero children Unbound, one child descend, more than one Ambiguous naming every candidate -- never a nearest match, never first-wins. WHAT MAKES THIS A WALL RATHER THAN A CHECK: the only value that can carry a terminal declaration is produced by consuming every segment, so a head that bound while a segment did not has no representation as a resolved path. A caller cannot read a provider file off a partial descent, because a partial descent yields a refusal and refusals carry no declaration.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ReferencePathResolution { + ReferencePathResolved { + terminal: Rc, + }, + ReferencePathHeadUnbound { + head_name: String, + span: Rc, + }, + ReferencePathHeadAmbiguous { + head_name: String, + span: Rc, + candidates: Rc>, + }, + ReferencePathSegmentUnbound { + segment: Rc, + parent: OccurrenceId, + }, + ReferencePathSegmentAmbiguous { + segment: Rc, + parent: OccurrenceId, + candidates: Rc>>, + }, + ReferencePathBuildRefused { + reason: Rc, + }, + ReferencePathNotANamespaceProjection { + head: Rc, + }, +} + +pub fn declaration_occurrence_ids(xs: Rc>>) -> Rc> { + v1_rt::reverse(xs.clone().iter().cloned().fold( + Rc::new(vec![]), + |acc: _, declaration: Rc| { + Rc::new({ + let mut __cons_v = (*acc).clone(); + __cons_v.insert(0, declaration.occurrence.clone()); + __cons_v + }) + }, + )) +} + +pub fn head_candidate_declarations( + index: Rc, + reference: Rc, + head_name: String, +) -> Rc>> { + { + let on_chain = match v1_rt::map_get(&index.declarations_by_name.clone(), head_name.clone()) + { + None => Rc::new(vec![]), + Some(same_spelling) => Rc::new({ + let mut __result = Vec::new(); + for declaration in v1_rt::reverse(same_spelling.clone()).iter().cloned() { + if declaration_exposed_on_reference_chain( + declaration.clone(), + reference.clone(), + index.module_by_occurrence.clone(), + index.exposure_by_occurrence.clone(), + index.order_by_occurrence.clone(), + ) { + __result.push(declaration); + } + } + __result + }), + }; + let named_modules = match v1_rt::map_get( + &index.module_declarations_by_segment.clone(), + module_declaration_last_segment(head_name.clone()), + ) { + None => Rc::new(vec![]), + Some(modules) => Rc::new({ + let mut __result = Vec::new(); + for declaration in v1_rt::reverse(modules.clone()).iter().cloned() { + if module_declaration_named_by_qualifier( + index.clone(), + declaration.clone(), + head_name.clone(), + ) { + __result.push(declaration); + } + } + __result + }), + }; + v1_rt::concat(on_chain.clone(), named_modules.clone()) + } +} + +pub fn declaration_children_named( + transport: Rc, + index: Rc, + parent: OccurrenceId, + name: String, +) -> Rc>> { + v1_rt::reverse(transport.declarations.clone().iter().cloned().fold( + Rc::new(vec![]), + |acc: _, declaration: Rc| { + let __fm = v1_rt::reverse(declaration.containment.clone().ancestors.clone()); + if __fm.is_empty() { + acc.clone() + } else { + let declared_parent = (*__fm)[0].clone(); + match (declared_parent.value.clone() == parent.value.clone()) { + false => acc.clone(), + true => match v1_rt::map_get( + &index.entries_by_id.clone(), + declaration.occurrence.clone().value.clone(), + ) { + None => acc.clone(), + Some(entry) => { + match (entry.projection.clone().authored_name.clone() == name.clone()) { + true => Rc::new({ + let mut __cons_v = (*acc.clone()).clone(); + __cons_v.insert(0, declaration.clone()); + __cons_v + }), + false => acc.clone(), + } + } + }, + } + } + }, + )) +} + +pub fn reference_path_descend_segments( + mut transport: Rc, + mut index: Rc, + mut current: Rc, + mut segments: Rc>>, +) -> Rc { + loop { + { + let __fm = segments.clone(); + if __fm.is_empty() { + break Rc::new(ReferencePathResolution::ReferencePathResolved { + terminal: current.clone(), + }); + } else { + let segment = (*__fm)[0].clone(); + let rest: Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); + { + let __fm = declaration_children_named( + transport.clone(), + index.clone(), + current.occurrence.clone(), + segment.name.clone(), + ); + if __fm.is_empty() { + break Rc::new(ReferencePathResolution::ReferencePathSegmentUnbound { + segment: segment.clone(), + parent: current.occurrence.clone(), + }); + } else { + let only = (*__fm)[0].clone(); + let others: Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); + { + let __fm = others.clone(); + if __fm.is_empty() { + { + let __tco_0 = only.clone(); + let __tco_1 = rest.clone(); + current = __tco_0; + segments = __tco_1; + continue; + } + } else { + break Rc::new( + ReferencePathResolution::ReferencePathSegmentAmbiguous { + segment: segment.clone(), + parent: current.occurrence.clone(), + candidates: Rc::new({ + let mut __cons_v = (*others.clone()).clone(); + __cons_v.insert(0, only.clone()); + __cons_v + }), + }, + ); + } + } + } + } + } + } + } +} + +pub fn resolve_reference_path( + transport: Rc, + index: Rc, + reference: Rc, +) -> Rc { + match (*reference_path_of( + index.clone(), + transport.references.clone(), + reference.clone(), + )) + .clone() + { + ReferencePathBuild::ReferencePathRefused { reason: reason, .. } => { + Rc::new(ReferencePathResolution::ReferencePathBuildRefused { + reason: reason.clone(), + }) + } + ReferencePathBuild::ReferencePathBuilt { path: path, .. } => { + let __fm = head_candidate_declarations( + index.clone(), + reference.clone(), + path.head_name.clone(), + ); + if __fm.is_empty() { + Rc::new(ReferencePathResolution::ReferencePathHeadUnbound { + head_name: path.head_name.clone(), + span: path.head_span.clone(), + }) + } else { + let only = (*__fm)[0].clone(); + let others: Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); + { + let __fm = others.clone(); + if __fm.is_empty() { + { + let __fm = path.segments.clone(); + if __fm.is_empty() { + Rc::new(ReferencePathResolution::ReferencePathResolved { + terminal: only.clone(), + }) + } else { + match only.category.clone() { + OccurrenceCategory::NamespaceSegmentOccurrence => reference_path_descend_segments(transport.clone(), index.clone(), only.clone(), path.segments.clone()), + _ => Rc::new(ReferencePathResolution::ReferencePathNotANamespaceProjection { + head: only.clone(), +}), +} + } + } + } else { + Rc::new(ReferencePathResolution::ReferencePathHeadAmbiguous { + head_name: path.head_name.clone(), + span: path.head_span.clone(), + candidates: declaration_occurrence_ids(Rc::new({ + let mut __cons_v = (*others.clone()).clone(); + __cons_v.insert(0, only.clone()); + __cons_v + })), + }) + } + } + } + } + } +} + +pub fn reference_base_child( + references: Rc>>, + reference: Rc, +) -> Option> { + references + .clone() + .iter() + .cloned() + .fold( + None, + |found: _, candidate: Rc| match found.clone() { + Some(_) => found.clone(), + None => { + let __fm = v1_rt::reverse(candidate.containment.clone().ancestors.clone()); + if __fm.is_empty() { + None + } else { + let parent = (*__fm)[0].clone(); + match (parent.value.clone() == reference.occurrence.clone().value.clone()) { + true => Some(candidate.clone()), + false => None, + } + } + } + }, + ) +} + +pub fn module_declaration_named_by_reference( + index: Rc, + base: Rc, +) -> Option> { + match v1_rt::map_get( + &index.entries_by_id.clone(), + base.occurrence.clone().value.clone(), + ) { + None => None, + Some(entry) => match v1_rt::map_get( + &index.module_declarations_by_segment.clone(), + entry.projection.clone().authored_name.clone(), + ) { + None => None, + Some(modules) => { + let __fm = modules.clone(); + if __fm.is_empty() { + None + } else { + let only = (*__fm)[0].clone(); + let rest: Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); + { + let __fm = rest.clone(); + if __fm.is_empty() { + Some(only.clone()) + } else { + None + } + } + } + } + }, + } +} + +pub fn module_member_candidate_ids( + index: Rc, + provider: Rc, + segment_name: String, +) -> Rc> { + match v1_rt::map_get( + &index.entries_by_id.clone(), + provider.occurrence.clone().value.clone(), + ) { + None => Rc::new(vec![]), + Some(provider_entry) => { + match v1_rt::map_get(&index.declarations_by_name.clone(), segment_name.clone()) { + None => Rc::new(vec![]), + Some(same_spelling) => Rc::new({ + let mut __result = Vec::new(); + for declaration in Rc::new({ + let mut __result = Vec::new(); + for declaration in v1_rt::reverse(same_spelling.clone()).iter().cloned() { + if declaration_projected_from_named_module( + declaration.clone(), + provider_entry.projection.clone().authored_name.clone(), + index.exposure_by_occurrence.clone(), + ) { + __result.push(declaration); + } + } + __result + }) + .iter() + .cloned() + { + __result.push(declaration.occurrence.clone()); + } + __result + }), + } + } + } +} + +pub fn reference_dependency_admission_for_category( + category: OccurrenceCategory, +) -> Rc { + match (*occurrence_category_clause_e_dependency_inducing_verdict(category.clone())).clone() { + OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyInducing => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyAdmittedClauseE), + OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyNotInducing { category: excluded, .. } => match excluded.clone() { + OccurrenceCategory::TypeOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyAdmittedTypeReference), + OccurrenceCategory::FieldOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: excluded.clone(), +}), + OccurrenceCategory::MethodOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: excluded.clone(), +}), + OccurrenceCategory::LexicalValueOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: excluded.clone(), +}), + OccurrenceCategory::CallableOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: excluded.clone(), +}), + OccurrenceCategory::ConstructorOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: excluded.clone(), +}), + OccurrenceCategory::NamespaceSegmentOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: excluded.clone(), +}), + OccurrenceCategory::ModuleValueOccurrence => Rc::new(ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: excluded.clone(), +}), +}, +} +} + +pub fn resolve_type_reference_via_structural_candidates_note() -> String { + thread_local! { + static CACHED: String = { + "The N2 typed entry with the index SUPPLIED instead of rebuilt. resolve_type_reference_containment_binding takes inputs and builds a candidate index per call, which is correct for a single-reference API and is a cost-shape defect inside a walk: one index per reference over a whole transport is quadratic in the transport, and DESIGN section 6 fixes a proven cost shape regardless of the realized n. The category guard is the SAME contract -- a non-TypeOccurrence reference refuses with ReferenceBindingProjectionWrongCategory before any candidate is enumerated -- so this is the same decision on a shared index, not a second one.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn resolve_type_reference_via_structural_candidates( + transport: Rc, + index: Rc, + reference: Rc, +) -> Rc { + match reference.category.clone() { + OccurrenceCategory::TypeOccurrence => resolve_reference_via_structural_candidates( + transport.clone(), + index.clone(), + reference.clone(), + ), + _ => Rc::new( + ReferenceBindingProjection::ReferenceBindingProjectionWrongCategory { + occurrence: reference.occurrence.clone(), + observed: reference.category.clone(), + }, + ), + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct JoinedDependencyBindingBuild { + pub projections_reversed: Rc>>, +} + +pub fn joined_dependency_binding_walk_note() -> String { + thread_local! { + static CACHED: String = { + "ONE POPULATION, TWO ADMISSION ROUTES. Clause-(e) references and type references reach the SAME BoundReferencePopulation through the same fold over the same candidate index: the projections are concatenated in transport order and bound_reference_population_from_projections is applied once. That is what makes this a join rather than two closures -- a second population would need its own totality law, its own refusal ordering and its own file projection, and the two could then disagree about one file.\n\nThe index is built ONCE for the whole walk. Excluded categories contribute nothing and are not errors: a field or method reference is not a dependency-inducing position, which is a fact about the position and not a failure to bind.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn joined_dependency_binding_walk_selected( + transport: Rc, + inputs: Rc, + references: Rc>>, +) -> Rc { + match (*occurrence_candidate_index_build(transport.clone(), inputs.clone())).clone() { + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexTransportRefused { refusal: refusal, .. } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: Rc::new(StructuralBindingIndexRefusal::StructuralBindingTransportRefusal { + refusal: refusal.clone(), +}), +}), + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexModulePathRefused { refusal: refusal, .. } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: Rc::new(StructuralBindingIndexRefusal::StructuralBindingModulePathRefusal { + refusal: refusal.clone(), +}), +}), + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexExposureRefused { refusal: refusal, .. } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: Rc::new(StructuralBindingIndexRefusal::StructuralBindingExposureRefusal { + refusal: refusal.clone(), +}), +}), + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexAuthoredOrderRefused { refusal: refusal, .. } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: Rc::new(StructuralBindingIndexRefusal::StructuralBindingAuthoredOrderRefusal { + refusal: refusal.clone(), +}), +}), + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexDeclarationBucketRefused { occurrence: occurrence, .. } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: Rc::new(StructuralBindingIndexRefusal::StructuralBindingDeclarationBucketRefusal { + occurrence: occurrence.clone(), +}), +}), + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexReady { index: index, .. } => Rc::new(StructuralBindingWalk::StructuralBindingWalkReady { + population: bound_reference_population_from_projections(v1_rt::reverse(references.clone().iter().cloned().fold(Rc::new(JoinedDependencyBindingBuild { + projections_reversed: Rc::new(vec![]), +}), |acc: Rc, reference: Rc| match (*reference_dependency_admission_for_category(reference.category.clone())).clone() { + ReferenceDependencyAdmission::ReferenceDependencyAdmittedClauseE => Rc::new(JoinedDependencyBindingBuild { + projections_reversed: v1_rt::concat(Rc::new(vec![resolve_reference_via_structural_candidates(transport.clone(), index.clone(), reference.clone())]), acc.projections_reversed.clone()), +}), + ReferenceDependencyAdmission::ReferenceDependencyAdmittedTypeReference => Rc::new(JoinedDependencyBindingBuild { + projections_reversed: v1_rt::concat(Rc::new(vec![resolve_type_reference_via_structural_candidates(transport.clone(), index.clone(), reference.clone())]), acc.projections_reversed.clone()), +}), + ReferenceDependencyAdmission::ReferenceDependencyExcluded { category: _, .. } => match reference_base_child(references.clone(), reference.clone()) { + None => acc.clone(), + Some(base) => match module_declaration_named_by_reference(index.clone(), base.clone()) { + None => acc.clone(), + Some(provider) => Rc::new(JoinedDependencyBindingBuild { + projections_reversed: v1_rt::concat(Rc::new(vec![resolve_module_member_projection(transport.clone(), index.clone(), reference.clone(), provider.clone())]), acc.projections_reversed.clone()), +}), +}, +}, +}).projections_reversed.clone())), +}), +} +} + +pub fn joined_dependency_binding_walk( + transport: Rc, + inputs: Rc, +) -> Rc { + joined_dependency_binding_walk_selected( + transport.clone(), + inputs.clone(), + transport.references.clone(), + ) +} + +pub fn reference_derived_dependency_binding_reference_filter_note() -> String { + thread_local! { + static CACHED: String = { + "Clause-(e) structural walks bind only reference categories that occurrence_category_clause_e_dependency_inducing_verdict classifies as OccurrenceCategoryClauseEDependencyInducing. TypeOccurrence references are N2's resolve_type_reference_containment_binding lane — parser-produced return-type annotations must not refuse the whole population when Int is not module-declared. Parser witnesses that need per-name resolution already avoid whole-transport walks for this reason; production assembled-closure projection applies the same boundary here.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn reference_derived_dependency_binding_references( + references: Rc>>, +) -> Rc>> { + { + let build = references.clone().iter().cloned().fold(Rc::new(ReferenceDerivedDependencyBindingReferenceBuild { + references_reversed: Rc::new(vec![]), +}), |acc: Rc, reference: Rc| match (*occurrence_category_clause_e_dependency_inducing_verdict(reference.category.clone())).clone() { + OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyInducing => Rc::new(ReferenceDerivedDependencyBindingReferenceBuild { + references_reversed: v1_rt::concat(Rc::new(vec![reference.clone()]), acc.references_reversed.clone()), +}), + OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyNotInducing { category: _, .. } => acc.clone(), +}); + v1_rt::reverse(build.references_reversed.clone()) + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ReferenceDerivedDependencyBindingReferenceBuild { + pub references_reversed: Rc>>, +} + +pub fn structural_binding_walk_selected_references( + transport: Rc, + inputs: Rc, + references: Rc>>, +) -> Rc { + match (*occurrence_candidate_index_build(transport.clone(), inputs.clone())).clone() { + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexTransportRefused { + refusal: refusal, + .. + } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: Rc::new( + StructuralBindingIndexRefusal::StructuralBindingTransportRefusal { + refusal: refusal.clone(), + }, + ), + }), + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexModulePathRefused { + refusal: refusal, + .. + } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: Rc::new( + StructuralBindingIndexRefusal::StructuralBindingModulePathRefusal { + refusal: refusal.clone(), + }, + ), + }), + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexExposureRefused { + refusal: refusal, .. } => Rc::new(StructuralBindingWalk::StructuralBindingWalkRefused { refusal: Rc::new( @@ -2989,6 +4239,15 @@ pub fn reference_derived_clause_e_production_from_assembled_closure( } } +pub fn reference_binding_ambiguous_candidates_note() -> String { + thread_local! { + static CACHED: String = { + "AMBIGUITY CARRIES ITS CANDIDATES, and the reason it must is that the information was already computed and then discarded here. resolve_reference_occurrence_binding returns OccurrenceAmbiguous { occurrence, candidates }, and this projection used to match `candidates: _` -- so a caller learned THAT a reference was ambiguous and never WHICH declarations competed, which is the fact an operator needs to discharge it and the fact a pool-independence claim is actually about. A count is not an identity: two ambiguity outcomes with the same cardinality over different declarations are different states with different remedies.\n\nThe carrier is the upstream AmbiguousBindingCandidates itself rather than a local list, for two reasons. It is the single authority for this shape, so re-spelling it here would be the nicknaming DESIGN section 3 forbids; and it is head-plus-second-plus-tail, so a one-candidate `ambiguity` has no constructor -- at-least-two-ness is structural rather than checked, and an arm that fabricated a single-candidate ambiguity cannot be written.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(tag = "_variant")] pub enum ReferenceBindingProjection { @@ -3000,6 +4259,7 @@ pub enum ReferenceBindingProjection { }, ReferenceBindingProjectionAmbiguous { occurrence: OccurrenceId, + candidates: Rc>, }, ReferenceBindingProjectionTransportRefused { refusal: Rc, @@ -3025,6 +4285,50 @@ pub enum ReferenceBindingProjection { }, } +pub fn reference_binding_projection_occurrence( + projection: Rc, +) -> Option { + match (*projection.clone()).clone() { + ReferenceBindingProjection::ReferenceBindingProjectionBound { + provider: provider, .. + } => Some(provider.reference_occurrence.clone()), + ReferenceBindingProjection::ReferenceBindingProjectionUnbound { + occurrence: occurrence, + .. + } => Some(occurrence.clone()), + ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { occurrence, .. } => { + Some(occurrence.clone()) + } + ReferenceBindingProjection::ReferenceBindingProjectionDeclarationBucketRefused { + occurrence: occurrence, + .. + } => Some(occurrence.clone()), + ReferenceBindingProjection::ReferenceBindingProjectionModulePathMissing { + occurrence: occurrence, + .. + } => Some(occurrence.clone()), + ReferenceBindingProjection::ReferenceBindingProjectionWrongCategory { + occurrence, .. + } => Some(occurrence.clone()), + ReferenceBindingProjection::ReferenceBindingProjectionTransportRefused { + refusal: _, + .. + } => None, + ReferenceBindingProjection::ReferenceBindingProjectionModulePathRefused { + refusal: _, + .. + } => None, + ReferenceBindingProjection::ReferenceBindingProjectionExposureRefused { + refusal: _, + .. + } => None, + ReferenceBindingProjection::ReferenceBindingProjectionAuthoredOrderRefused { + refusal: _, + .. + } => None, + } +} + pub fn resolve_type_reference_containment_binding_note() -> String { thread_local! { static CACHED: String = { @@ -3063,53 +4367,89 @@ pub fn resolve_reference_via_structural_candidates_note() -> String { CACHED.with(|c: &String| c.clone()) } -pub fn resolve_reference_via_structural_candidates( +pub fn resolve_module_member_projection( transport: Rc, index: Rc, reference: Rc, + provider: Rc, ) -> Rc { { - let candidates = candidate_occurrence_ids_for_reference(index.clone(), reference.clone()); - match (*resolve_reference_occurrence_binding( + let segment_name = match v1_rt::map_get( + &index.entries_by_id.clone(), + reference.occurrence.clone().value.clone(), + ) { + None => "".to_string(), + Some(entry) => entry.projection.clone().authored_name.clone(), + }; + resolve_reference_from_candidates( transport.clone(), - reference.occurrence.clone(), - candidates.clone(), - )) - .clone() - { - OccurrenceReferenceBindingOutcome::OccurrenceReferenceBindingTransportRefused { - refusal: refusal, - .. - } => Rc::new( - ReferenceBindingProjection::ReferenceBindingProjectionTransportRefused { - refusal: refusal.clone(), + index.clone(), + reference.clone(), + module_member_candidate_ids(index.clone(), provider.clone(), segment_name.clone()), + ) + } +} + +pub fn resolve_reference_via_structural_candidates( + transport: Rc, + index: Rc, + reference: Rc, +) -> Rc { + resolve_reference_from_candidates( + transport.clone(), + index.clone(), + reference.clone(), + candidate_occurrence_ids_for_reference(index.clone(), reference.clone()), + ) +} + +pub fn resolve_reference_from_candidates( + transport: Rc, + index: Rc, + reference: Rc, + candidates: Rc>, +) -> Rc { + match (*resolve_reference_occurrence_binding( + transport.clone(), + reference.occurrence.clone(), + candidates.clone(), + )) + .clone() + { + OccurrenceReferenceBindingOutcome::OccurrenceReferenceBindingTransportRefused { + refusal: refusal, + .. + } => Rc::new( + ReferenceBindingProjection::ReferenceBindingProjectionTransportRefused { + refusal: refusal.clone(), + }, + ), + OccurrenceReferenceBindingOutcome::OccurrenceReferenceBindingDecided { + result: result, + .. + } => match (*result.clone()).clone() { + OccurrenceBindingResult::OccurrenceUnbound { occurrence: _, .. } => Rc::new( + ReferenceBindingProjection::ReferenceBindingProjectionUnbound { + occurrence: reference.occurrence.clone(), }, ), - OccurrenceReferenceBindingOutcome::OccurrenceReferenceBindingDecided { - result: result, - .. - } => match (*result.clone()).clone() { - OccurrenceBindingResult::OccurrenceUnbound { occurrence: _, .. } => Rc::new( - ReferenceBindingProjection::ReferenceBindingProjectionUnbound { - occurrence: reference.occurrence.clone(), - }, - ), - OccurrenceBindingResult::OccurrenceAmbiguous { .. } => Rc::new( - ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { - occurrence: reference.occurrence.clone(), - }, - ), - OccurrenceBindingResult::OccurrenceBound { - binding: binding, .. - } => { - let declaration_occurrence = binding - .candidate - .clone() - .containment - .clone() - .terminal - .clone(); - match module_of_occurrence(index.module_by_occurrence.clone(), reference.occurrence.clone()) { + OccurrenceBindingResult::OccurrenceAmbiguous { candidates, .. } => Rc::new( + ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { + occurrence: reference.occurrence.clone(), + candidates: candidates.clone(), + }, + ), + OccurrenceBindingResult::OccurrenceBound { + binding: binding, .. + } => { + let declaration_occurrence = binding + .candidate + .clone() + .containment + .clone() + .terminal + .clone(); + match module_of_occurrence(index.module_by_occurrence.clone(), reference.occurrence.clone()) { None => Rc::new(ReferenceBindingProjection::ReferenceBindingProjectionModulePathMissing { occurrence: reference.occurrence.clone(), }), @@ -3127,9 +4467,8 @@ pub fn resolve_reference_via_structural_candidates( }), }, } - } - }, - } + } + }, } } @@ -3623,8 +4962,6 @@ pub struct NamespaceStructuralRootExposure; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ModuleLocalMemberExposure; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct CrossFileProviderExportedExposure; -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Section13StructuralVisibility; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Section13SiblingArmExclusion; diff --git a/src/v1/stage0/src/std_occurrence_binding_resolve.rs b/src/v1/stage0/src/std_occurrence_binding_resolve.rs index ff977d763a6..01dab430bea 100644 --- a/src/v1/stage0/src/std_occurrence_binding_resolve.rs +++ b/src/v1/stage0/src/std_occurrence_binding_resolve.rs @@ -12,7 +12,7 @@ pub use crate::std_occurrence_binding::{ pub use crate::std_occurrence_identity::occurrence_transport_validate; use crate::std_occurrence_identity::OccurrenceCategory::{ CallableOccurrence, ConstructorOccurrence, FieldOccurrence, LexicalValueOccurrence, - MethodOccurrence, NamespaceSegmentOccurrence, TypeOccurrence, + MethodOccurrence, ModuleValueOccurrence, NamespaceSegmentOccurrence, TypeOccurrence, }; use crate::std_occurrence_identity::OccurrenceRole::{DeclarationRole, ReferenceRole}; use crate::std_occurrence_identity::OccurrenceTransportRefusal::{ @@ -131,6 +131,12 @@ pub fn occurrence_category_binding_verdict( OccurrenceCategory::ConstructorOccurrence => { Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) } + OccurrenceCategory::ModuleValueOccurrence => { + Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) + } + OccurrenceCategory::NamespaceSegmentOccurrence => { + Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) + } _ => Rc::new( OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingInadmissible { reference: reference.clone(), @@ -164,6 +170,18 @@ pub fn occurrence_category_binding_verdict( OccurrenceCategory::FieldOccurrence => { Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) } + OccurrenceCategory::ModuleValueOccurrence => { + Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) + } + OccurrenceCategory::CallableOccurrence => { + Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) + } + OccurrenceCategory::TypeOccurrence => { + Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) + } + OccurrenceCategory::ConstructorOccurrence => { + Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) + } _ => Rc::new( OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingInadmissible { reference: reference.clone(), @@ -182,6 +200,17 @@ pub fn occurrence_category_binding_verdict( }, ), }, + OccurrenceCategory::ModuleValueOccurrence => match declaration.clone() { + OccurrenceCategory::ModuleValueOccurrence => { + Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) + } + _ => Rc::new( + OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingInadmissible { + reference: reference.clone(), + declaration: declaration.clone(), + }, + ), + }, OccurrenceCategory::NamespaceSegmentOccurrence => match declaration.clone() { OccurrenceCategory::NamespaceSegmentOccurrence => { Rc::new(OccurrenceCategoryBindingVerdict::OccurrenceCategoryBindingAdmissible) diff --git a/src/v1/stage0/src/std_occurrence_identity.rs b/src/v1/stage0/src/std_occurrence_identity.rs index 68f793bf0a9..dcb64ecd9fa 100644 --- a/src/v1/stage0/src/std_occurrence_identity.rs +++ b/src/v1/stage0/src/std_occurrence_identity.rs @@ -107,6 +107,7 @@ pub enum OccurrenceCategory { NamespaceSegmentOccurrence, FieldOccurrence, MethodOccurrence, + ModuleValueOccurrence, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -127,7 +128,7 @@ impl OccurrenceCategoryModuleScopeExposureVerdict { pub fn occurrence_category_module_scope_exposure_verdict_note() -> String { thread_local! { static CACHED: String = { - "Canonical module-scope exposure surface for declaration categories (review 45716 predicate-dissolution): consumers match OccurrenceCategoryModuleScopeExposureVerdict directly — not a parallel Bool predicate over the OccurrenceCategory coproduct. CallableOccurrence, TypeOccurrence, ConstructorOccurrence, and NamespaceSegmentOccurrence are module-scope members; LexicalValueOccurrence, FieldOccurrence, and MethodOccurrence are not.".to_string() + "Canonical module-scope exposure surface for declaration categories (review 45716 predicate-dissolution): consumers match OccurrenceCategoryModuleScopeExposureVerdict directly — not a parallel Bool predicate over the OccurrenceCategory coproduct. CallableOccurrence, TypeOccurrence, ConstructorOccurrence, NamespaceSegmentOccurrence and ModuleValueOccurrence are module-scope members; LexicalValueOccurrence, FieldOccurrence, and MethodOccurrence are not. ModuleValueOccurrence exists because a module-scope `data name: T = v` is a module MEMBER and LexicalValueOccurrence is explicitly not one: reusing the lexical arm would have declared every module datum unexported, and reusing CallableOccurrence would have nicknamed a value as callable. Before it existed the item was classified Unclassified and bound no name at all, so a cross-file reference to any module datum could not bind and every module-value edge was missing from the derived closure.".to_string() }; } CACHED.with(|c: &String| c.clone()) @@ -164,6 +165,9 @@ pub fn occurrence_category_module_scope_exposure_verdict( category: category.clone(), }, ), + OccurrenceCategory::ModuleValueOccurrence => Rc::new( + OccurrenceCategoryModuleScopeExposureVerdict::OccurrenceCategoryModuleScopeExposed, + ), } } @@ -185,7 +189,7 @@ impl OccurrenceCategoryClauseEDependencyInducingVerdict { pub fn occurrence_category_clause_e_dependency_inducing_verdict_note() -> String { thread_local! { static CACHED: String = { - "Canonical clause-(e) reference-category filter surface (review 50193 predicate-dissolution): consumers match OccurrenceCategoryClauseEDependencyInducingVerdict directly — not a parallel inline match over the OccurrenceCategory coproduct. LexicalValueOccurrence, CallableOccurrence, ConstructorOccurrence, and NamespaceSegmentOccurrence references may induce cross-file file dependencies; TypeOccurrence is N2's resolve_type_reference_containment_binding lane; FieldOccurrence and MethodOccurrence do not participate in clause-(e) structural walks.".to_string() + "Canonical clause-(e) reference-category filter surface (review 50193 predicate-dissolution): consumers match OccurrenceCategoryClauseEDependencyInducingVerdict directly — not a parallel inline match over the OccurrenceCategory coproduct. LexicalValueOccurrence, CallableOccurrence, ConstructorOccurrence, NamespaceSegmentOccurrence and ModuleValueOccurrence references may induce cross-file file dependencies; TypeOccurrence is N2's resolve_type_reference_containment_binding lane; FieldOccurrence and MethodOccurrence do not participate in clause-(e) structural walks.".to_string() }; } CACHED.with(|c: &String| c.clone()) @@ -208,6 +212,7 @@ pub fn occurrence_category_clause_e_dependency_inducing_verdict( OccurrenceCategory::MethodOccurrence => Rc::new(OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyNotInducing { category: category.clone(), }), + OccurrenceCategory::ModuleValueOccurrence => Rc::new(OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyInducing), } } @@ -261,6 +266,30 @@ pub struct OccurrenceTransport { pub references: Rc>>, } +pub fn reference_path_note() -> String { + thread_local! { + static CACHED: String = { + "ONE CARRIER FOR BOTH SPELLINGS OF A PROJECTION. The dot is containment projection, and the two syntactic positions that write one spell it differently: a TYPE path arrives as a single occurrence whose authored_name is the whole dotted text (live_tree.LiveTreeDisposition), while an EXPRESSION path arrives as a base reference plus one FieldOccurrence node per segment, the base reachable as the segment's containment child. ReferencePath is what both fold into, so the resolution rule is authored once and neither position carries a private one. The head is a NAME plus the span that located it, not an occurrence: the type spelling has no separate occurrence for its head, and pretending it does would mint an identity the parser never allocated. A segment likewise carries its own span where one exists and the whole path's span where the spelling gives it none -- an honestly coarse location, never a fabricated offset into a blob. THE LAW THE SHAPE ENFORCES: a path with no segments is an ordinary bare reference, and every segment is a step that must land on exactly one containment child of the previous declaration -- so no terminal declaration, no binding, and no dependency-ready outcome from a head that merely bound. What this replaces is two admissions that stood in for it: the value-reference arm admitting a namespace category (the head widen) and the field-reference arm admitting module-member categories (the segment widen); both were the reference site guessing at a question the path shape decides.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ReferencePathSegment { + pub name: String, + pub diagnostic_span: Rc, + pub occurrence: Option, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ReferencePath { + pub head_name: String, + pub head_span: Rc, + pub head_occurrence: OccurrenceId, + pub segments: Rc>>, +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct OccurrenceIdentityAcceptanceLaw { pub id: String, @@ -965,6 +994,8 @@ pub struct FieldOccurrence; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct MethodOccurrence; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ModuleValueOccurrence; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct DeclarationRole; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ReferenceRole; diff --git a/src/v1/stage0/src/std_reference_derived_closure.rs b/src/v1/stage0/src/std_reference_derived_closure.rs new file mode 100644 index 00000000000..aec7ef22640 --- /dev/null +++ b/src/v1/stage0/src/std_reference_derived_closure.rs @@ -0,0 +1,633 @@ +// Generated by v1 compiler -- do not edit. +// Source module: std.reference_derived_closure + +use self::ReferenceDerivedClosureFixedPoint::*; +use self::ReferenceDerivedClosureRefusal::*; +use self::ReferenceDerivedClosureStep::*; +use crate::std_occurrence_binding_candidates::AssembledCrossFileBindingClosure::{ + AssembledCrossFileBindingClosureReady, AssembledCrossFileBindingClosureRefused, +}; +use crate::std_occurrence_binding_candidates::AssembledCrossFileBindingClosureRefusal::*; +use crate::std_occurrence_binding_candidates::BoundReferencePopulation::{ + AllReferencesBound, ReferencePopulationRefused, +}; +use crate::std_occurrence_binding_candidates::CrossFileBindingProvenanceProjection::*; +use crate::std_occurrence_binding_candidates::ModulePathFileIndex::{ + ModulePathFileIndexReady, ModulePathFileIndexRefused, +}; +use crate::std_occurrence_binding_candidates::ModulePathFileIndexRefusal::*; +use crate::std_occurrence_binding_candidates::OccurrenceModulePathIndexRefusal::*; +use crate::std_occurrence_binding_candidates::ReferenceDerivedDependencyProjection::{ + ReferenceDerivedDependencyProjectionBindingRefused, + ReferenceDerivedDependencyProjectionFileRefused, ReferenceDerivedDependencyProjectionReady, +}; +use crate::std_occurrence_binding_candidates::StructuralBindingIndexRefusal::*; +use crate::std_occurrence_binding_candidates::StructuralBindingWalk::{ + StructuralBindingWalkReady, StructuralBindingWalkRefused, +}; +pub use crate::std_occurrence_binding_candidates::{ + assemble_cross_file_binding_closure, file_for_module_path, + joined_dependency_binding_walk_selected, module_by_occurrence_build, module_of_occurrence, + module_path_file_index_from_rows, reference_binding_projection_occurrence, + reference_derived_dependency_projection, +}; +pub use crate::std_occurrence_binding_candidates::{ + AssembledCrossFileBindingClosure, AssembledCrossFileBindingClosureRefusal, + BoundReferencePopulation, CrossFileBindingClosureRow, CrossFileBindingProvenance, + CrossFileBindingProvenanceProjection, DirectFileDependency, ModulePathFileIndex, + ModulePathFileIndexRefusal, OccurrenceBindingCandidateInputs, OccurrenceModulePathIndexRefusal, + ReferenceDerivedDependencyProjection, StructuralBindingIndexRefusal, StructuralBindingWalk, +}; +pub use crate::std_occurrence_identity::{ + OccurrenceId, OccurrenceIndexEntry, OccurrenceProjection, OccurrenceTransport, + ReferenceOccurrence, +}; +use crate::std_types::Bool::*; +pub use crate::std_types::{Bool, FilePath, List, NonEmptyStr}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ReferenceDerivedClosureState { + pub admitted_files: Rc>, + pub universe: Rc>>, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ParsedFileSupply { + pub file: FilePath, + pub row: Rc, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ReferenceDerivedClosureRefusal { + ClosureProviderFileOutsideUniverse { + file: FilePath, + }, + ClosureNoEntryAdmitted, + ClosureAssemblyRefused { + refusal: Rc, + }, + ClosureWalkRefused { + refusal: Rc, + }, + ClosureBindingRefused { + population: Rc, + first_failure_locus: Rc, + failure_count: i64, + }, + ClosureRefusalNotLocatable { + occurrence: OccurrenceId, + }, + ClosurePopulationFailureHasNoOccurrence { + population: Rc, + }, + ClosureFileProjectionRefused { + first_failure: Rc, + more_failures: Rc>>, + }, + ClosureModuleStorageRefused { + refusal: Rc, + }, + ClosureOccurrenceModulePathRefused { + refusal: Rc, + }, + ClosureReferenceFileUnresolved { + occurrence: OccurrenceId, + }, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ReferenceDerivedClosureStep { + ClosureStepAdvanced { + state: Rc, + newly_pending: Rc>, + edges: Rc>>, + provenance: Rc>>, + }, + ClosureStepSettled { + state: Rc, + edges: Rc>>, + provenance: Rc>>, + }, + ClosureStepRefused { + refusal: Rc, + }, +} + +pub fn no_file_paths() -> Rc> { + Rc::new(vec![]) +} + +pub fn no_selected_references() -> Rc>> { + Rc::new(vec![]) +} + +pub fn file_path_in(files: Rc>, file: String) -> bool { + files + .clone() + .iter() + .cloned() + .fold(false, |found: bool, candidate: String| { + (found || (candidate.clone() == file.clone())) + }) +} + +pub fn universe_row_for_file( + universe: Rc>>, + file: String, +) -> Option> { + universe + .clone() + .iter() + .cloned() + .fold(None, |found: _, row: Rc| { + match found.clone() { + Some(_) => found.clone(), + None => match (row.file.clone() == file.clone()) { + true => Some(row.clone()), + false => None, + }, + } + }) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct UniversePartition { + pub consumer: Option>, + pub rest: Rc>>, +} + +pub fn partition_universe( + universe: Rc>>, + entry: String, +) -> Rc { + universe.clone().iter().cloned().fold( + Rc::new(UniversePartition { + consumer: None, + rest: Rc::new(vec![]), + }), + |acc: Rc, supply: Rc| match (supply.file.clone() + == entry.clone()) + { + true => Rc::new(UniversePartition { + consumer: Some(supply.row.clone()), + rest: acc.rest.clone(), + }), + false => Rc::new(UniversePartition { + consumer: acc.consumer.clone(), + rest: v1_rt::concat(acc.rest.clone(), Rc::new(vec![supply.row.clone()])), + }), + }, + ) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct SelectedReferenceBuild { + pub references: Rc>>, + pub unresolved: Option, +} + +pub fn selected_references_for_admitted_files( + transport: Rc, + module_by_occurrence: Rc>, + module_files: Rc>, + admitted: Rc>, +) -> Rc { + transport.references.clone().iter().cloned().fold( + Rc::new(SelectedReferenceBuild { + references: no_selected_references(), + unresolved: None, + }), + |acc: Rc, reference: Rc| match acc + .unresolved + .clone() + { + Some(_) => acc.clone(), + None => match module_of_occurrence( + module_by_occurrence.clone(), + reference.occurrence.clone(), + ) { + None => Rc::new(SelectedReferenceBuild { + references: acc.references.clone(), + unresolved: Some(reference.occurrence.clone()), + }), + Some(module_path) => { + match file_for_module_path(module_files.clone(), module_path.clone()) { + None => Rc::new(SelectedReferenceBuild { + references: acc.references.clone(), + unresolved: Some(reference.occurrence.clone()), + }), + Some(file) => match file_path_in(admitted.clone(), file.clone()) { + false => acc.clone(), + true => Rc::new(SelectedReferenceBuild { + references: v1_rt::concat( + acc.references.clone(), + Rc::new(vec![reference.clone()]), + ), + unresolved: None, + }), + }, + } + } + }, + }, + ) +} + +pub fn locate_occurrence( + transport: Rc, + occurrence: OccurrenceId, +) -> Option> { + transport + .index + .clone() + .entries + .clone() + .iter() + .cloned() + .fold( + None, + |found: _, entry: Rc| match found.clone() { + Some(_) => found.clone(), + None => match (entry.projection.clone().occurrence.clone().value.clone() + == occurrence.value.clone()) + { + true => Some(entry.projection.clone()), + false => None, + }, + }, + ) +} + +pub fn population_first_failure_occurrence( + population: Rc, +) -> Option { + match (*population.clone()).clone() { + BoundReferencePopulation::AllReferencesBound { providers: _, .. } => None, + BoundReferencePopulation::ReferencePopulationRefused { + first_failure: failure, + .. + } => reference_binding_projection_occurrence(failure.clone()), + } +} + +pub fn population_failure_count(population: Rc) -> i64 { + match (*population.clone()).clone() { + BoundReferencePopulation::AllReferencesBound { providers: _, .. } => 0, + BoundReferencePopulation::ReferencePopulationRefused { + more_failures: more, + .. + } => (1 + (more.clone().len() as i64)), + } +} + +pub fn closure_binding_refusal( + transport: Rc, + population: Rc, +) -> Rc { + match population_first_failure_occurrence(population.clone()) { + None => Rc::new( + ReferenceDerivedClosureRefusal::ClosurePopulationFailureHasNoOccurrence { + population: population.clone(), + }, + ), + Some(occurrence) => match locate_occurrence(transport.clone(), occurrence.clone()) { + None => Rc::new(ReferenceDerivedClosureRefusal::ClosureRefusalNotLocatable { + occurrence: occurrence.clone(), + }), + Some(locus) => Rc::new(ReferenceDerivedClosureRefusal::ClosureBindingRefused { + population: population.clone(), + first_failure_locus: locus.clone(), + failure_count: population_failure_count(population.clone()), + }), + }, + } +} + +pub fn provider_files_of(edges: Rc>>) -> Rc> { + edges.clone().iter().cloned().fold( + no_file_paths(), + |acc: Rc>, edge: Rc| match file_path_in( + acc.clone(), + edge.provider_file.clone(), + ) { + true => acc.clone(), + false => v1_rt::concat(acc.clone(), Rc::new(vec![edge.provider_file.clone()])), + }, + ) +} + +pub fn unseen_provider_files( + providers: Rc>, + admitted: Rc>, +) -> Rc> { + providers.clone().iter().cloned().fold( + no_file_paths(), + |acc: Rc>, provider: String| match (file_path_in( + admitted.clone(), + provider.clone(), + ) || file_path_in( + acc.clone(), + provider.clone(), + )) { + true => acc.clone(), + false => v1_rt::concat(acc.clone(), Rc::new(vec![provider.clone()])), + }, + ) +} + +pub fn first_file_outside_universe( + files: Rc>, + universe: Rc>>, +) -> Option { + files + .clone() + .iter() + .cloned() + .fold(None, |found: _, file: String| match found.clone() { + Some(_) => found.clone(), + None => match universe_row_for_file(universe.clone(), file.clone()) { + Some(_) => None, + None => Some(file.clone()), + }, + }) +} + +pub fn reference_derived_closure_step( + state: Rc, +) -> Rc { + match state.admitted_files.clone().first().cloned() { + None => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureNoEntryAdmitted), + }), + Some(entry) => { + let partition = partition_universe(state.universe.clone(), entry.clone()); + match partition.consumer.clone() { + None => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureNoEntryAdmitted), + }), + Some(consumer_row) => match (*assemble_cross_file_binding_closure( + consumer_row.clone(), + partition.rest.clone(), + )) + .clone() + { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: refusal, + .. + } => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureAssemblyRefused { + refusal: refusal.clone(), + }), + }), + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + transport, + inputs, + module_files: module_file_rows, + .. + } => { + match (*module_path_file_index_from_rows(module_file_rows.clone())).clone() + { + ModulePathFileIndex::ModulePathFileIndexRefused { + refusal: refusal, + .. + } => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new( + ReferenceDerivedClosureRefusal::ClosureModuleStorageRefused { + refusal: refusal.clone(), + }, + ), + }), + ModulePathFileIndex::ModulePathFileIndexReady { + entries: module_files, + .. + } => { + let module_index = + module_by_occurrence_build(inputs.module_paths.clone()); + match module_index.refusal.clone() { + Some(refusal) => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureOccurrenceModulePathRefused { + refusal: refusal.clone(), +}), +}), + None => { + let selected = selected_references_for_admitted_files(transport.clone(), module_index.module_by_occurrence.clone(), module_files.clone(), state.admitted_files.clone()); +match selected.unresolved.clone() { + Some(occurrence) => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureReferenceFileUnresolved { + occurrence: occurrence.clone(), +}), +}), + None => match (*joined_dependency_binding_walk_selected(transport.clone(), inputs.clone(), selected.references.clone())).clone() { + StructuralBindingWalk::StructuralBindingWalkRefused { refusal: refusal, .. } => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureWalkRefused { + refusal: refusal.clone(), +}), +}), + StructuralBindingWalk::StructuralBindingWalkReady { population: population, .. } => match (*reference_derived_dependency_projection(population.clone(), module_files.clone())).clone() { + ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionBindingRefused { population: refused, .. } => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: closure_binding_refusal(transport.clone(), refused.clone()), +}), + ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionFileRefused { first_failure, more_failures, .. } => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureFileProjectionRefused { + first_failure: first_failure.clone(), + more_failures: more_failures.clone(), +}), +}), + ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionReady { binding_provenance: provenance, file_dependencies: edges, .. } => { + let newly_pending = unseen_provider_files(provider_files_of(edges.clone()), state.admitted_files.clone()); +match first_file_outside_universe(newly_pending.clone(), state.universe.clone()) { + Some(missing) => Rc::new(ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: Rc::new(ReferenceDerivedClosureRefusal::ClosureProviderFileOutsideUniverse { + file: missing.clone(), +}), +}), + None => { + let next = Rc::new(ReferenceDerivedClosureState { + admitted_files: v1_rt::concat(state.admitted_files.clone(), newly_pending.clone()), + universe: state.universe.clone(), +}); +match ((newly_pending.clone().len() as i64) == 0) { + true => Rc::new(ReferenceDerivedClosureStep::ClosureStepSettled { + state: next.clone(), + edges: edges.clone(), + provenance: provenance.clone(), +}), + false => Rc::new(ReferenceDerivedClosureStep::ClosureStepAdvanced { + state: next.clone(), + newly_pending: newly_pending.clone(), + edges: edges.clone(), + provenance: provenance.clone(), +}), +} +}, +} +}, +}, +}, +} +}, +} + } + } + } + }, + } + } + } +} + +pub fn reference_derived_closure_initial( + entry: String, + universe: Rc>>, +) -> Rc { + Rc::new(ReferenceDerivedClosureState { + admitted_files: Rc::new(vec![entry.clone()]), + universe: universe.clone(), + }) +} + +pub fn closure_step_is_settled(step: Rc) -> bool { + match (*step.clone()).clone() { + ReferenceDerivedClosureStep::ClosureStepSettled { .. } => true, + ReferenceDerivedClosureStep::ClosureStepAdvanced { .. } => false, + ReferenceDerivedClosureStep::ClosureStepRefused { refusal: _, .. } => false, + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ReferenceDerivedClosureFixedPoint { + ClosureFixedPointSettled { + admitted_files: Rc>, + edges: Rc>>, + provenance: Rc>>, + iterations: i64, + }, + ClosureFixedPointRefused { + refusal: Rc, + iterations: i64, + }, + ClosureFixedPointExhausted { + admitted_files: Rc>, + iterations: i64, + }, +} +impl ReferenceDerivedClosureFixedPoint { + pub fn iterations(&self) -> i64 { + match self { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + iterations: __val, + .. + } => __val.clone(), + ReferenceDerivedClosureFixedPoint::ClosureFixedPointRefused { + iterations: __val, + .. + } => __val.clone(), + ReferenceDerivedClosureFixedPoint::ClosureFixedPointExhausted { + iterations: __val, + .. + } => __val.clone(), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ClosureFixedPointFold { + pub state: Rc, + pub step: Option>, + pub iterations: i64, +} + +pub fn reference_derived_closure_fixed_point( + entry: String, + universe: Rc>>, +) -> Rc { + { + let initial = reference_derived_closure_initial(entry.clone(), universe.clone()); + let run = universe.clone().iter().cloned().fold( + Rc::new(ClosureFixedPointFold { + state: initial.clone(), + step: Some(reference_derived_closure_step(initial.clone())), + iterations: 1, + }), + |acc: Rc, _ignored: Rc| match acc.step.clone() + { + Some(settled_or_refused) => { + match closure_step_is_settled(settled_or_refused.clone()) { + true => acc.clone(), + false => match (*settled_or_refused.clone()).clone() { + ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: _, .. + } => acc.clone(), + ReferenceDerivedClosureStep::ClosureStepSettled { .. } => acc.clone(), + ReferenceDerivedClosureStep::ClosureStepAdvanced { + state: advanced, + .. + } => { + let next = reference_derived_closure_step(advanced.clone()); + Rc::new(ClosureFixedPointFold { + state: advanced.clone(), + step: Some(next.clone()), + iterations: (acc.iterations.clone() + 1), + }) + } + }, + } + } + None => { + let next = reference_derived_closure_step(acc.state.clone()); + Rc::new(ClosureFixedPointFold { + state: acc.state.clone(), + step: Some(next.clone()), + iterations: (acc.iterations.clone() + 1), + }) + } + }, + ); + match run.step.clone() { + None => Rc::new( + ReferenceDerivedClosureFixedPoint::ClosureFixedPointExhausted { + admitted_files: Rc::new(vec![]), + iterations: run.iterations.clone(), + }, + ), + Some(final_step) => match (*final_step.clone()).clone() { + ReferenceDerivedClosureStep::ClosureStepSettled { + state, + edges, + provenance, + .. + } => Rc::new( + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + admitted_files: state.admitted_files.clone(), + edges: edges.clone(), + provenance: provenance.clone(), + iterations: run.iterations.clone(), + }, + ), + ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: refusal, .. + } => Rc::new( + ReferenceDerivedClosureFixedPoint::ClosureFixedPointRefused { + refusal: refusal.clone(), + iterations: run.iterations.clone(), + }, + ), + ReferenceDerivedClosureStep::ClosureStepAdvanced { state, .. } => Rc::new( + ReferenceDerivedClosureFixedPoint::ClosureFixedPointExhausted { + admitted_files: state.admitted_files.clone(), + iterations: run.iterations.clone(), + }, + ), + }, + } + } +} diff --git a/src/v1/stage0/src/v1_compiler_parse.rs b/src/v1/stage0/src/v1_compiler_parse.rs index ec8561e473b..56c749310cb 100644 --- a/src/v1/stage0/src/v1_compiler_parse.rs +++ b/src/v1/stage0/src/v1_compiler_parse.rs @@ -11,8 +11,8 @@ use self::ParserResultWitness::*; pub use crate::extdeps_languages_dag_syntax::{dag_non_name_keywords, dag_syntax_spec}; pub use crate::std_algebra::FreeMonoid; use crate::std_occurrence_identity::OccurrenceCategory::{ - CallableOccurrence, FieldOccurrence, LexicalValueOccurrence, MethodOccurrence, - NamespaceSegmentOccurrence, TypeOccurrence, + CallableOccurrence, ConstructorOccurrence, FieldOccurrence, LexicalValueOccurrence, + MethodOccurrence, ModuleValueOccurrence, NamespaceSegmentOccurrence, TypeOccurrence, }; pub use crate::std_occurrence_identity::{ alloc_occurrence_id, authored_token_ordinal_space_from_allocator, @@ -2422,7 +2422,7 @@ pub fn stamp_parsed_pattern( field_bindings.clone(), ancestors.clone(), ctx.clone(), - Rc::new(ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceReference { category: OccurrenceCategory::FieldOccurrence, }), ); @@ -2524,6 +2524,65 @@ pub fn parsed_occurrence_role_for_node( } } +pub fn parsed_module_item_declares_type_name(item: Rc) -> bool { + (((((item.transport.clone() == None) && (item.body.clone() == None)) + && (item.ident_span.clone() != None)) + && (item.name.clone() != "".to_string())) + && ((item.connective.clone() != Connective::NoConnective) + || (item.inferred.clone() != None))) +} + +pub fn parsed_module_item_declares_value_name(item: Rc) -> bool { + (((((item.transport.clone() == None) && (item.body.clone() != None)) + && (item.type_annotation.clone() != None)) + && (item.ident_span.clone() != None)) + && (item.name.clone() != "".to_string())) +} + +pub fn parsed_module_item_role(item: Rc) -> Rc { + if parsed_module_item_declares_type_name(item.clone()) { + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::TypeOccurrence, + }) + } else { + if parsed_module_item_declares_value_name(item.clone()) { + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::ModuleValueOccurrence, + }) + } else { + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceUnclassified) + } + } +} + +pub fn stamp_parsed_module_item_list( + nodes: Rc>>, + ancestors: Rc>, + ctx: Rc, +) -> Rc { + nodes.clone().iter().cloned().fold( + Rc::new(ParsedNodeListStampResult { + nodes: Rc::new(vec![]), + ctx: ctx.clone(), + }), + |acc: Rc, node: Rc| { + let acc = v1_rt::take_owned(acc); + { + let stamped = stamp_parsed_node( + node.clone(), + ancestors.clone(), + acc.ctx, + parsed_module_item_role(node.clone()), + ); + Rc::new(ParsedNodeListStampResult { + nodes: v1_rt::rc_list_push(acc.nodes, stamped.node.clone()), + ctx: stamped.ctx.clone(), + }) + } + }, + ) +} + pub fn parse_context_record_occurrence( ctx: Rc, role: Rc, @@ -2657,6 +2716,62 @@ pub fn stamp_parsed_node_children( }), Rc::new(ParsedOccurrenceRole::ParsedOccurrenceUnclassified), ), + ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::ModuleValueOccurrence, + .. + } => stamp_parsed_node_list_with_head_role( + node.children.clone(), + ancestors.clone(), + ctx.clone(), + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceReference { + category: OccurrenceCategory::TypeOccurrence, + }), + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceUnclassified), + ), + ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::NamespaceSegmentOccurrence, + .. + } => { + stamp_parsed_module_item_list(node.children.clone(), ancestors.clone(), ctx.clone()) + } + ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::TypeOccurrence, + .. + } => match node.connective.clone() { + Connective::Disj => stamp_parsed_node_list( + node.children.clone(), + ancestors.clone(), + ctx.clone(), + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::ConstructorOccurrence, + }), + ), + Connective::Conj => stamp_parsed_node_list( + node.children.clone(), + ancestors.clone(), + ctx.clone(), + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::FieldOccurrence, + }), + ), + _ => stamp_parsed_node_list( + node.children.clone(), + ancestors.clone(), + ctx.clone(), + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceUnclassified), + ), + }, + ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::ConstructorOccurrence, + .. + } => stamp_parsed_node_list( + node.children.clone(), + ancestors.clone(), + ctx.clone(), + Rc::new(ParsedOccurrenceRole::ParsedOccurrenceDeclaration { + category: OccurrenceCategory::FieldOccurrence, + }), + ), _ => stamp_parsed_node_list( node.children.clone(), ancestors.clone(), diff --git a/src/v1/stage0/src/v1_gunbc_namespace_reference_derived_closure_production_observations.rs b/src/v1/stage0/src/v1_gunbc_namespace_reference_derived_closure_production_observations.rs index 23496ca7c29..3e7dcd97426 100644 --- a/src/v1/stage0/src/v1_gunbc_namespace_reference_derived_closure_production_observations.rs +++ b/src/v1/stage0/src/v1_gunbc_namespace_reference_derived_closure_production_observations.rs @@ -2,7 +2,10 @@ // Source module: v1.gunbc.namespace_reference_derived_closure_production_observations use self::NrdfcParsed::*; -pub use crate::std_occurrence_binding_candidates::OccurrenceBindingCandidateInputs; +use crate::std_occurrence_binding_candidates::DeclarationExposureGrounding::ModuleLocalMemberExposure; +pub use crate::std_occurrence_binding_candidates::{ + DeclarationExposureGrounding, OccurrenceBindingCandidateInputs, +}; pub use crate::std_occurrence_identity::{OccurrenceId, OccurrenceTransport}; pub use crate::std_reference_binding_observation::structural_binding_resolution_from_candidates; use crate::std_reference_binding_observation::ReferenceBindingObservation::{ @@ -92,6 +95,7 @@ pub fn nrdfc_parse(file: String, source: String) -> Rc { inputs: occurrence_binding_inputs_from_transport( module_path.clone(), transport.clone(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, ), }), } diff --git a/src/v1/stage0/src/v1_gunbc_occurrence_binding_parser_walk.rs b/src/v1/stage0/src/v1_gunbc_occurrence_binding_parser_walk.rs index 97b624d31c3..9ccf80ad935 100644 --- a/src/v1/stage0/src/v1_gunbc_occurrence_binding_parser_walk.rs +++ b/src/v1/stage0/src/v1_gunbc_occurrence_binding_parser_walk.rs @@ -127,9 +127,19 @@ pub fn occurrence_binding_inputs_accumulator_note() -> String { CACHED.with(|c: &String| c.clone()) } +pub fn occurrence_binding_inputs_grounding_note() -> String { + thread_local! { + static CACHED: String = { + "THE GROUNDING IS A PARAMETER BECAUSE IT IS A FACT ABOUT THE FILE'S ROLE IN THE CLOSURE, NOT ABOUT THE PARSE. It was hardwired to ModuleLocalMemberExposure, which derives ModuleExposure for module-root declarations — visible only to references in the SAME module. That is right for a consumer's own declarations and wrong for a PROVIDER in a cross-file closure, whose exported declarations must derive RootExposure. With it hardwired, an import-less consumer could never bind to a declaration in another file: the candidate population was empty by construction, and the failure looked like an ordinary Unbound rather than a missing exposure grounding.\n\nFound by execution, not by reading: the checkpoint-1 bridge witness resolved a real LiveTreeDisposition reference against the real v2.std.live_tree module and got Unbound while its provider-withheld control behaved correctly, which is the signature of the provider being present but unexposed.\n\nDeclarationExposure's own authority requires the grounding be chosen AT DERIVATION, never as a post-hoc upgrade of an already-derived row, which is exactly why this is a parameter here rather than a rewrite of the rows afterwards. Every caller passes ModuleLocalMemberExposure for an ordinary module member, whether the file is the entry or a provider: exposure is a fact about the declaration's own containment, never about the role the file plays in one closure, and role-stamped exposure made a file's accepted declarations depend on which file was the entry.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + pub fn occurrence_binding_inputs_from_transport( module_path: String, transport: Rc, + grounding: DeclarationExposureGrounding, ) -> Rc { Rc::new(OccurrenceBindingCandidateInputs { module_paths: v1_rt::reverse( @@ -162,7 +172,8 @@ pub fn occurrence_binding_inputs_from_transport( exposure: declaration_exposure_from_containment( module_path.clone(), declaration.containment.clone(), - DeclarationExposureGrounding::ModuleLocalMemberExposure, + grounding.clone(), + declaration.category.clone(), ), })]), acc, diff --git a/src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs b/src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs new file mode 100644 index 00000000000..c5610315205 --- /dev/null +++ b/src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs @@ -0,0 +1,1856 @@ +// Generated by v1 compiler -- do not edit. +// Source module: v1.tests.claim.import_deletion_bridge_witness + +use self::IdbBridge::*; +pub use crate::std_occurrence_binding::{AmbiguousBindingCandidates, BindingCandidate}; +use crate::std_occurrence_binding_candidates::AssembledCrossFileBindingClosure::{ + AssembledCrossFileBindingClosureReady, AssembledCrossFileBindingClosureRefused, +}; +use crate::std_occurrence_binding_candidates::AssembledCrossFileBindingClosureRefusal::AssembledCrossFileBindingClosureModulePathFileRefused; +use crate::std_occurrence_binding_candidates::BoundReferencePopulation::{ + AllReferencesBound, ReferencePopulationRefused, +}; +use crate::std_occurrence_binding_candidates::CrossFileBindingProvenancePopulation::{ + AllCrossFileBindingsBound, CrossFileBindingPopulationBindingRefused, + CrossFileBindingPopulationRefused, +}; +use crate::std_occurrence_binding_candidates::DeclarationExposureGrounding::ModuleLocalMemberExposure; +use crate::std_occurrence_binding_candidates::ModulePathFileIndex::{ + ModulePathFileIndexReady, ModulePathFileIndexRefused, +}; +use crate::std_occurrence_binding_candidates::ModulePathFileIndexRefusal::ModulePathFileConflict; +use crate::std_occurrence_binding_candidates::OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexReady; +use crate::std_occurrence_binding_candidates::ReferenceBindingProjection::{ + ReferenceBindingProjectionAmbiguous, ReferenceBindingProjectionBound, + ReferenceBindingProjectionUnbound, +}; +use crate::std_occurrence_binding_candidates::ReferenceDependencyAdmission::{ + ReferenceDependencyAdmittedClauseE, ReferenceDependencyAdmittedTypeReference, + ReferenceDependencyExcluded, +}; +use crate::std_occurrence_binding_candidates::ReferenceDerivedDependencyProjection::{ + ReferenceDerivedDependencyProjectionBindingRefused, + ReferenceDerivedDependencyProjectionFileRefused, ReferenceDerivedDependencyProjectionReady, +}; +use crate::std_occurrence_binding_candidates::ReferencePathResolution::{ + ReferencePathBuildRefused, ReferencePathHeadAmbiguous, ReferencePathHeadUnbound, + ReferencePathNotANamespaceProjection, ReferencePathResolved, ReferencePathSegmentAmbiguous, + ReferencePathSegmentUnbound, +}; +use crate::std_occurrence_binding_candidates::StructuralBindingIndexRefusal::*; +use crate::std_occurrence_binding_candidates::StructuralBindingWalk::{ + StructuralBindingWalkReady, StructuralBindingWalkRefused, +}; +pub use crate::std_occurrence_binding_candidates::{ + assemble_cross_file_binding_closure, cross_file_binding_provenance_from_bound_population, + direct_file_dependencies_from_provenances, joined_dependency_binding_walk, + module_path_file_index_from_rows, occurrence_candidate_index_build, + occurrence_category_clause_e_dependency_inducing_verdict, + reference_dependency_admission_for_category, reference_derived_dependency_projection, + resolve_reference_binding_via_structural_candidates, resolve_reference_path, + structural_binding_walk_selected_references, +}; +pub use crate::std_occurrence_binding_candidates::{ + AssembledCrossFileBindingClosure, AssembledCrossFileBindingClosureRefusal, + BoundReferencePopulation, BoundReferenceProvider, CrossFileBindingClosureRow, + CrossFileBindingProvenance, CrossFileBindingProvenancePopulation, DeclarationExposureGrounding, + DirectFileDependency, ModulePathFileIndex, ModulePathFileIndexRefusal, ModulePathFileRow, + OccurrenceBindingCandidateInputs, OccurrenceCandidateIndexBuild, ReferenceBindingProjection, + ReferenceDependencyAdmission, ReferenceDerivedDependencyProjection, ReferencePathResolution, + StructuralBindingIndexRefusal, StructuralBindingWalk, +}; +use crate::std_occurrence_identity::OccurrenceCategory::{ + CallableOccurrence, ConstructorOccurrence, FieldOccurrence, LexicalValueOccurrence, + MethodOccurrence, NamespaceSegmentOccurrence, TypeOccurrence, +}; +pub use crate::std_occurrence_identity::OccurrenceCategoryClauseEDependencyInducingVerdict; +use crate::std_occurrence_identity::OccurrenceCategoryClauseEDependencyInducingVerdict::{ + OccurrenceCategoryClauseEDependencyInducing, OccurrenceCategoryClauseEDependencyNotInducing, +}; +pub use crate::std_occurrence_identity::{ + DeclarationOccurrence, OccurrenceCategory, OccurrenceId, OccurrenceIndexEntry, + OccurrenceTransport, ReferenceOccurrence, +}; +use crate::std_types::Bool::*; +pub use crate::std_types::{Bool, FilePath, List, NonEmptyStr}; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource; +use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource::{ + ParsedOccurrenceBindingSourceReady, ParsedOccurrenceBindingSourceRefused, +}; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::{ + occurrence_binding_inputs_from_transport, parse_authored_occurrence_binding_source, +}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +pub fn stage0_emission_model_gap_note() -> String { + thread_local! { + static CACHED: String = { + "BLOCKER FOUND WHILE MAKING THIS BRANCH MECHANICALLY COMPLETE — an emission-model gap, not a registration oversight, and it is the reason regen refuses with this witness present.\n\nReading real source requires filesystem_read, which requires the Filesystem SERVICE in the import closure. Any service in the v1 emission closure makes the emitter generate `dry_run.rs` — service dry-run support (`pub struct DryRunMode(pub bool)`), verified by emitting fresh and reading the file's own header rather than inferred from the name.\n\n`dry_run.rs` HAS NO SOURCE MODULE. Every other emitted stage0 file corresponds to one (`v1_rt.rs` is module v1.rt), and `gunbc.stage0_emit_plan` derives the plan FROM the parse-observed module closure — so a file emitted as a consequence of service PRESENCE, rather than from a module, is structurally invisible to the derivation. regen therefore refuses it as unregistered, and correctly refuses the obvious repair too: the CI auto-heal hand-added two rows to the projection, which is exactly what the refusal text forbids.\n\nNo src/v1 module used a service before, which is why this never surfaced. It is not caused by this lane and is not this lane's to fix casually: the plan model must learn that service presence in the closure contributes emitter-support files, or those files must be classified through v2.compiler.self_host.frontier. Either is a change to a load-bearing regen gate and needs its own modelling, not a path added here.\n\nCONSEQUENCE FOR THIS WITNESS, stated rather than worked around: it cannot both live under src/v1 (needed to import v1.gunbc.occurrence_binding_parser_walk, which the dag+src/v2 compile-clean roots cannot see) and read the live tree (which drags a service into the v1 seed). That is a genuine placement contradiction, not a filing preference, and it is the next thing to resolve.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn import_deletion_bridge_offline_recipe() -> String { + thread_local! { + static CACHED: String = { + "OFFLINE LOCAL RECIPE (requires the v1 parser pool, exactly as the sibling occurrence_binding_parser_walk witness does). This file lives under src/v1/tests/claim/ and NOT under dag/test/claim/ for a structural reason, not a filing preference: it imports v1.gunbc.occurrence_binding_parser_walk, and the compile-clean gate compiles dag + src/v2 only, so a copy under dag/ fails to resolve the module it exists to exercise. Placing it here is the same resolution the sibling witness already made.\n\n target/release/claim_batch --source-root dag --source-root src/v1 --source-root src/v2 --entry src/v1/tests/claim/import_deletion_bridge_witness_test.dag --claim-run --wet\n\nTHE FUNCTION LIST IS DELIBERATELY NOT ENUMERATED. This file is enrolled file-grain, so the empty-function authority expands it, and a hand-copied roster of test fn names beside a file that already names them is a second representation that rots on the next rename. An earlier revision carried such a list, and it named three functions -- two of which no longer exist and one of which was described as an expected red. NOTHING HERE IS EXPECTED RED ANY MORE: the positive keystone greened when role-directed minting landed, and the provider is SUPPLIED by this file rather than discovered from the tree, which is stated on the keystone itself so the green is not read as provider discovery.\n\nSTATED PLAINLY BECAUSE IT IS A REAL COVERAGE GAP, NOT A DETAIL: witnesses here are not per-PR discovery-enrolled, so these controls execute locally and on demand rather than on every change. DISSOLVE-ON is the sibling's, unchanged and shared rather than re-minted: witness_layer_roots admits src/v1 for parser-transport witnesses without Node ambiguity, or the execution corpus carries per-entry source roots -- then this recipe deletes and discovery enrollment suffices.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn bridge_witness_note() -> String { + thread_local! { + static CACHED: String = { + "CHECKPOINT 1 OF IMPORT DELETION — the bridge from the real source population to production occurrence binding, proven on the largest falsifier rather than on a fixture pair.\n\nWHAT THIS PROVES, AND WHAT IT DOES NOT. It is a SUPPLIED-PROVIDER binding keystone: the provider path is an INPUT to the operation under test, so it establishes that ordinary parser facts can bind across files when handed the right provider. It does NOT establish provider DISCOVERY, and it does not yet project a provider-file edge — it checks provider module identity only. Those are separate milestones and this witness must not be read as closing them.\n\nWHAT THIS PROVES. An import-less consumer reaches a declaration in a REAL production module — v2.std.live_tree, read off disk, not a fixture provider and not an embedded source string — with no import participating in the decision anywhere. LiveTreeDisposition is the subject because it is the largest population that loses its sole provider under strip: 1,673 references corpus-wide. If reference-derived binding cannot reach this declaration, deleting imports cannot work at all.\n\nSCOPE REDUCED, LOUDLY, WITH A TRIGGER. An earlier revision read BOTH files off disk, so the provider was the ordinary production module rather than anything authored for this test — strictly better evidence. It is not possible today: filesystem_read needs the Filesystem service, and a service in the v1 emission closure emits an unmodelled `dry_run.rs` (see the gap note above), while the witness must live under src/v1 to import the module it exercises at all. Both sources are therefore EMBEDDED, exactly as the sibling parser-walk witness does.\n\nWHAT THAT COSTS, so nobody reads more into this than it carries: the provider is now an authored copy of v2.std.live_tree's type declaration, not the real module, so this no longer witnesses anything about the live corpus and cannot catch drift between the two. It proves the binding mechanism only. DISSOLVE-ON: the stage0 emission model accounts for service-support files (or classifies them through v2.compiler.self_host.frontier), after which both sides return to filesystem_read and the provider becomes the real module again.\n\nTHE PARSER FINDING BELOW DOES NOT DEPEND ON THIS. It was established by a structural census of v1.02_parse's declaration-minting sites, not by reading the corpus, so the reduction costs the keystone's reach and costs the finding nothing.\n\nWHY PER-REFERENCE RESOLVE AND NOT structural_binding_walk. The walk demands AllReferencesBound over the whole transport. A real production module references types this two-file closure does not contain, so the walk would refuse for reasons that have nothing to do with the claim — and a refusal read as a failure here would be a false red. Resolving the one reference under test keeps the discriminator on the claim.\n\nTHE DISCRIMINATING CONTROL IS THE SECOND TEST, NOT AN ASSERTION IN THIS ONE. Binding the reference proves little on its own: it must FAIL when the provider is absent from the assembled closure. Without that control this witness would pass on any closure that happened to contain the name, which is precisely the pool-membership coincidence the whole lane exists to eliminate.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_live_tree_source_path() -> String { + thread_local! { + static CACHED: String = { + "src/v2/std/live_tree.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_consumer_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/live_tree_consumer_bare.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_provider_module_name() -> String { + thread_local! { + static CACHED: String = { + "v2.std.live_tree".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_subject_name() -> String { + thread_local! { + static CACHED: String = { + "live_tree.LiveTreeDisposition".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_subject_leaf_name() -> String { + thread_local! { + static CACHED: String = { + "LiveTreeDisposition".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_missing_provider_name() -> String { + thread_local! { + static CACHED: String = { + "missing.MissingProviderType".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_consumer_source() -> String { + "module test.fixture.import_deletion_bridge.live_tree_consumer_bare\n\nfn bridge_consumer_reads_disposition(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n d\n}\n".to_string() +} + +pub fn idb_provider_source() -> String { + "module v2.std.live_tree\n\ntype LiveTreeDisposition\n = ReadsLiveTree\n | SubstrateInputsOnly\n".to_string() +} + +pub fn idb_totality_note() -> String { + thread_local! { + static CACHED: String = { + "THE TOTALITY LAW, and the defect that made it necessary.\n\nAn earlier revision of this witness reached its 2-provenance/1-edge result through a FAIL-OPEN ADAPTER: it matched only ReferenceBindingProjectionBound, sent every other outcome to a discard arm, and then hand-constructed AllReferencesBound from the surviving subset. That is the absorbing fallback DESIGN section 5 forbids, built into the evidence for a lane whose whole purpose is deleting a fail-open. It would have reported a complete closure for a population where one reference bound and another did not -- which is the false-skip direction, the one that silently under-loads.\n\nWHAT REPLACED IT. One assembly, then the production total path and nothing local: structural_binding_walk_selected_references returns a BoundReferencePopulation that is either AllReferencesBound or ReferencePopulationRefused carrying the first failure, and reference_derived_dependency_projection turns that into Ready or a typed binding/file refusal. This file never constructs AllReferencesBound, never folds projections itself, and never uses an empty list to mean a stage refused.\n\nWHY THE OUTCOME IS A COPRODUCT. Every stage that can refuse is carried as its own arm, reusing the production refusal types (StructuralBindingIndexRefusal, ModulePathFileIndexRefusal, ReferenceDerivedDependencyProjection's own refusal arms) rather than minting a parallel vocabulary. The two arms that are genuinely local are about the FIXTURE's own population -- an unknown candidate path and a reference-count mismatch -- because those are properties of the controlled input, not of the substrate under test.\n\nWHY THE SELECTION REFUSES ON A COUNT MISMATCH. The subject is mentioned exactly twice by construction. Operating on whichever references happen to be found would make the multiplicity claim self-fulfilling: a parser that stamped one reference would produce one row and the row count would agree with itself. Refusing unless exactly two are observed is what keeps the 2-to-1 law a claim about the projection rather than a restatement of the input.\n\nWHY NOT THE TOP-LEVEL HELPER. reference_derived_dependency_projection_for_assembled_closure selects its references through the clause-e dependency-inducing filter, which deliberately EXCLUDES TypeOccurrence -- that is the N2 lane. The subject here is a type reference, so that helper would select zero references, report Ready with zero provenance rows and zero edges, and every count assertion would pass vacuously. Passing the selected references to the walk directly is what avoids buying a green with an empty population; moving TypeOccurrence into clause e to reuse the walk is the wrong repair and belongs to nobody.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct IdbCandidate { + pub file: String, + pub source: String, +} + +pub fn idb_candidate_population_note() -> String { + thread_local! { + static CACHED: String = { + "THE CANDIDATE POPULATION, explicit rather than defaulted. An earlier revision resolved a path to source with `if path == consumer then consumer_source else provider_source`, so ANY path -- including one deliberately unrelated -- silently received the real provider's text. Under that shape an unrelated-candidate control cannot exist, because unrelated and actual provider are not representable as different contents, and a provider-discovery control would be actively misleading. A path absent from this population now REFUSES; it never defaults to provider contents.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_candidates() -> Rc>> { + Rc::new(vec![ + Rc::new(IdbCandidate { + file: idb_consumer_source_path(), + source: idb_consumer_source(), + }), + Rc::new(IdbCandidate { + file: idb_live_tree_source_path(), + source: idb_provider_source(), + }), + Rc::new(IdbCandidate { + file: idb_unrelated_source_path(), + source: idb_unrelated_source(), + }), + Rc::new(IdbCandidate { + file: idb_second_provider_source_path(), + source: idb_second_provider_source(), + }), + Rc::new(IdbCandidate { + file: idb_malformed_source_path(), + source: idb_malformed_source(), + }), + Rc::new(IdbCandidate { + file: idb_partial_consumer_source_path(), + source: idb_partial_consumer_source(), + }), + Rc::new(IdbCandidate { + file: idb_same_file_source_path(), + source: idb_same_file_source(), + }), + Rc::new(IdbCandidate { + file: idb_conflicting_source_path(), + source: idb_conflicting_source(), + }), + Rc::new(IdbCandidate { + file: idb_type_consumer_source_path(), + source: idb_type_consumer_source(), + }), + Rc::new(IdbCandidate { + file: idb_path_consumer_source_path(), + source: idb_path_consumer_source(), + }), + Rc::new(IdbCandidate { + file: idb_duplicate_member_source_path(), + source: idb_duplicate_member_source(), + }), + ]) +} + +pub fn idb_path_consumer_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/path_controls_consumer.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_path_consumer_source_note() -> String { + thread_local! { + static CACHED: String = { + "A DEDICATED consumer for the reference-path controls, deliberately not the type-join consumer those other witnesses count references in. Three references in one file: one that must resolve to a terminal declaration, one whose head resolves and whose segment names nothing, and one whose head names nothing at all. They share a file because the controls must be read against the SAME parse -- a segment refusal is only evidence that head and segment are distinguishable if a resolving path exists beside it in the same population.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_path_consumer_source() -> String { + "module test.fixture.import_deletion_bridge.path_controls_consumer\n\nfn reads_good(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n d\n}\n\nfn reads_missing_member(d: live_tree.NoSuchMember) -> live_tree.NoSuchMember {\n d\n}\n\nfn reads_missing_head(d: no_such_module.Anything) -> no_such_module.Anything {\n d\n}\n\nfn reads_ordinary_field(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n d.ordinary_field\n}\n\nfn reads_duplicated(t: duplicated.Twice) -> duplicated.Twice {\n t\n}\n\nfn reads_three_segments(d: live_tree.LiveTreeDisposition.ReadsLiveTree) -> live_tree.LiveTreeDisposition.ReadsLiveTree {\n d\n}\n\nfn reads_fully_qualified(d: v2.std.live_tree.LiveTreeDisposition) -> v2.std.live_tree.LiveTreeDisposition {\n d\n}\n".to_string() +} + +pub fn idb_unrelated_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/unrelated_module.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_unrelated_source() -> String { + "module test.fixture.import_deletion_bridge.unrelated_module\n\ntype UnrelatedCarrier\n = UnrelatedOne\n | UnrelatedTwo\n".to_string() +} + +pub fn idb_second_provider_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/second_live_tree.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_second_provider_source() -> String { + "module test.fixture.import_deletion_bridge.live_tree\n\ntype LiveTreeDisposition\n = ReadsLiveTree\n | SubstrateInputsOnly\n".to_string() +} + +pub fn idb_conflicting_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/live_tree_second_home.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_conflicting_source() -> String { + "module v2.std.live_tree\n\ntype LiveTreeDispositionElsewhere\n = ElsewhereOne\n | ElsewhereTwo\n".to_string() +} + +pub fn idb_malformed_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/malformed_provider.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_malformed_source() -> String { + "module test.fixture.import_deletion_bridge.malformed_provider\n\ntype = = =\n".to_string() +} + +pub fn idb_partial_consumer_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/partial_consumer.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_partial_consumer_source() -> String { + "module test.fixture.import_deletion_bridge.partial_consumer\n\nfn bridge_partial(a: live_tree.LiveTreeDisposition, b: missing.MissingProviderType) -> live_tree.LiveTreeDisposition {\n a\n}\n".to_string() +} + +pub fn idb_same_file_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/same_file.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_same_file_source() -> String { + "module test.fixture.import_deletion_bridge.same_file\n\ntype LiveTreeDisposition\n = ReadsLiveTree\n | SubstrateInputsOnly\n\nfn same_file_reads(d: LiveTreeDisposition) -> LiveTreeDisposition {\n d\n}\n".to_string() +} + +pub fn idb_source_for_candidate(path: String) -> Option { + idb_candidates() + .iter() + .cloned() + .fold(None, |found: _, candidate: Rc| { + match found.clone() { + Some(_) => found.clone(), + None => match (candidate.file.clone() == path.clone()) { + true => Some(candidate.source.clone()), + false => None, + }, + } + }) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum IdbBridge { + IdbBridgeProjected { + projection: Rc, + }, + IdbBridgeWalkRefused { + refusal: Rc, + }, + IdbBridgeIndexRefused { + refusal: Rc, + }, + IdbBridgeAssemblyRefused { + refusal: Rc, + }, + IdbBridgeParseRefused { + file: String, + }, + IdbBridgeCandidateUnknown { + file: String, + }, + IdbBridgeSelectionRefused { + observed: i64, + }, +} + +pub fn idb_parsed_row(path: String) -> Option> { + match idb_source_for_candidate(path.clone()) { + None => None, + Some(source) => Some(parse_authored_occurrence_binding_source( + path.clone(), + source.clone(), + )), + } +} + +pub fn idb_no_closure_rows() -> Rc>> { + Rc::new(vec![]) +} + +pub fn idb_closure_row_for( + path: String, + grounding: DeclarationExposureGrounding, +) -> Rc>> { + match idb_parsed_row(path.clone()).as_deref().cloned() { + None => idb_no_closure_rows(), + Some(ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused) => { + idb_no_closure_rows() + } + Some(ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport: t, + module_path: m, + .. + }) => Rc::new(vec![Rc::new(CrossFileBindingClosureRow { + transport: t.clone(), + inputs: occurrence_binding_inputs_from_transport( + m.clone(), + t.clone(), + grounding.clone(), + ), + module_path: m.clone(), + file_path: path.clone(), + })]), + } +} + +pub fn idb_index_entry_for_occurrence( + transport: Rc, + occurrence: OccurrenceId, +) -> Option> { + transport + .index + .clone() + .entries + .clone() + .iter() + .cloned() + .fold( + None, + |found: _, entry: Rc| match found.clone() { + Some(_) => found.clone(), + None => match (entry.projection.clone().occurrence.clone().value.clone() + == occurrence.value.clone()) + { + true => Some(entry.clone()), + false => None, + }, + }, + ) +} + +pub fn idb_no_references() -> Rc>> { + Rc::new(vec![]) +} + +pub fn idb_name_in(names: Rc>, name: String) -> bool { + names + .clone() + .iter() + .cloned() + .fold(false, |found: bool, candidate: String| { + (found || (candidate.clone() == name.clone())) + }) +} + +pub fn idb_references_in_names( + transport: Rc, + names: Rc>, +) -> Rc>> { + transport.references.clone().iter().cloned().fold( + idb_no_references(), + |acc: Rc>>, reference: Rc| { + match idb_index_entry_for_occurrence(transport.clone(), reference.occurrence.clone()) { + None => acc.clone(), + Some(entry) => match idb_name_in( + names.clone(), + entry.projection.clone().authored_name.clone(), + ) { + true => v1_rt::concat(acc.clone(), Rc::new(vec![reference.clone()])), + false => acc.clone(), + }, + } + }, + ) +} + +pub fn idb_references_named( + transport: Rc, + name: String, +) -> Rc>> { + transport.references.clone().iter().cloned().fold( + idb_no_references(), + |acc: Rc>>, reference: Rc| { + match idb_index_entry_for_occurrence(transport.clone(), reference.occurrence.clone()) { + None => acc.clone(), + Some(entry) => { + match (entry.projection.clone().authored_name.clone() == name.clone()) { + true => v1_rt::concat(acc.clone(), Rc::new(vec![reference.clone()])), + false => acc.clone(), + } + } + } + }, + ) +} + +pub fn idb_references_named_in_consumer(consumer_path: String) -> Rc>> { + match idb_parsed_row(consumer_path.clone()).as_deref().cloned() { + None => idb_no_references(), + Some(ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused) => { + idb_no_references() + } + Some(ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport: t, + .. + }) => idb_references_named(t.clone(), idb_subject_name()), + } +} + +pub fn idb_project_from_assembled( + closure: Rc, + subject_names: Rc>, + expected_references: i64, +) -> Rc { + match (*closure.clone()).clone() { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: refusal, + .. + } => Rc::new(IdbBridge::IdbBridgeAssemblyRefused { + refusal: refusal.clone(), + }), + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + transport: t, + inputs: i, + module_files: rows, + .. + } => { + let selected = idb_references_in_names(t.clone(), subject_names.clone()); + match ((selected.clone().len() as i64) == expected_references.clone()) { + false => Rc::new(IdbBridge::IdbBridgeSelectionRefused { + observed: (selected.clone().len() as i64), + }), + true => match (*module_path_file_index_from_rows(rows.clone())).clone() { + ModulePathFileIndex::ModulePathFileIndexRefused { + refusal: refusal, .. + } => Rc::new(IdbBridge::IdbBridgeIndexRefused { + refusal: refusal.clone(), + }), + ModulePathFileIndex::ModulePathFileIndexReady { + entries: entries, .. + } => match (*structural_binding_walk_selected_references( + t.clone(), + i.clone(), + selected.clone(), + )) + .clone() + { + StructuralBindingWalk::StructuralBindingWalkRefused { + refusal: refusal, + .. + } => Rc::new(IdbBridge::IdbBridgeWalkRefused { + refusal: refusal.clone(), + }), + StructuralBindingWalk::StructuralBindingWalkReady { + population: population, + .. + } => Rc::new(IdbBridge::IdbBridgeProjected { + projection: reference_derived_dependency_projection( + population.clone(), + entries.clone(), + ), + }), + }, + }, + } + } + } +} + +pub fn idb_bridge_for( + consumer_path: String, + provider_paths: Rc>, + subject_names: Rc>, + expected_references: i64, +) -> Rc { + match idb_source_for_candidate(consumer_path.clone()) { + None => Rc::new(IdbBridge::IdbBridgeCandidateUnknown { + file: consumer_path.clone(), + }), + Some(_) => { + match provider_paths + .clone() + .iter() + .cloned() + .fold(None, |missing: _, p: String| match missing.clone() { + Some(_) => missing.clone(), + None => match idb_source_for_candidate(p.clone()) { + None => Some(p.clone()), + Some(_) => None, + }, + }) { + Some(unknown) => Rc::new(IdbBridge::IdbBridgeCandidateUnknown { + file: unknown.clone(), + }), + None => match idb_closure_row_for( + consumer_path.clone(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ) + .first() + .cloned() + { + None => Rc::new(IdbBridge::IdbBridgeParseRefused { + file: consumer_path.clone(), + }), + Some(consumer_row) => { + let provider_rows = provider_paths.clone().iter().cloned().fold( + idb_no_closure_rows(), + |acc: Rc>>, p: String| { + v1_rt::concat( + acc, + idb_closure_row_for( + p.clone(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ), + ) + }, + ); + match ((provider_rows.clone().len() as i64) + == (provider_paths.clone().len() as i64)) + { + false => Rc::new(IdbBridge::IdbBridgeParseRefused { + file: idb_malformed_source_path(), + }), + true => idb_project_from_assembled( + assemble_cross_file_binding_closure( + consumer_row.clone(), + provider_rows.clone(), + ), + subject_names.clone(), + expected_references.clone(), + ), + } + } + }, + } + } + } +} + +pub fn idb_supplied_bridge() -> Rc { + idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![idb_live_tree_source_path()]), + Rc::new(vec![idb_subject_name()]), + 2, + ) +} + +pub fn idb_ready_projection( + bridge: Rc, +) -> Option> { + match (*bridge.clone()).clone() { + IdbBridge::IdbBridgeProjected { + projection: projection, + .. + } => Some(projection.clone()), + _ => None, + } +} + +pub fn idb_provenances(bridge: Rc) -> Rc>> { + match (*bridge.clone()).clone() { + IdbBridge::IdbBridgeProjected { ref projection, .. } => { + let ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionReady { + binding_provenance: rows, + .. + } = projection.as_ref() + else { + unreachable!() + }; + rows.clone() + } + _ => Rc::new(vec![]), + } +} + +pub fn idb_file_dependencies(bridge: Rc) -> Rc>> { + match (*bridge.clone()).clone() { + IdbBridge::IdbBridgeProjected { ref projection, .. } => { + let ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionReady { + file_dependencies: edges, + .. + } = projection.as_ref() + else { + unreachable!() + }; + edges.clone() + } + _ => Rc::new(vec![]), + } +} + +pub fn idb_is_ready(bridge: Rc) -> bool { + match (*bridge.clone()).clone() { + IdbBridge::IdbBridgeProjected { ref projection, .. } => { + let ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionReady { + .. + } = projection.as_ref() + else { + unreachable!() + }; + true + } + _ => false, + } +} + +pub fn idb_population_refused_unbound(bridge: Rc) -> bool { + match (*bridge.clone()).clone() { + IdbBridge::IdbBridgeProjected { ref projection, .. } => { + let ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionBindingRefused { population: population, .. } = projection.as_ref() else { unreachable!() }; + match (*population.clone()).clone() { + BoundReferencePopulation::ReferencePopulationRefused { + first_failure: failure, + .. + } => match (*failure.clone()).clone() { + ReferenceBindingProjection::ReferenceBindingProjectionUnbound { + occurrence: _, + .. + } => true, + _ => false, + }, + _ => false, + } + } + _ => false, + } +} + +pub fn witness_import_less_reference_reaches_the_supplied_provider() -> bool { + match (*idb_supplied_bridge()).clone() { + IdbBridge::IdbBridgeProjected { ref projection, .. } => { + let ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionReady { + binding_provenance: rows, + .. + } = projection.as_ref() + else { + unreachable!() + }; + rows.clone().iter().cloned().fold( + ((rows.clone().len() as i64) == 2), + |ok: bool, row: Rc| { + (ok && (row.provider_file.clone() == idb_live_tree_source_path())) + }, + ) + } + _ => false, + } +} + +pub fn witness_consumer_carries_no_import_declaration() -> bool { + { + let source = idb_consumer_source(); + (!v1_rt::string_contains(&source, "import ".to_string()) + && v1_rt::string_contains(&source, idb_subject_name())) + } +} + +pub fn witness_consumer_mentions_the_subject_twice() -> bool { + ((idb_references_named_in_consumer(idb_consumer_source_path()).len() as i64) == 2) +} + +pub fn witness_two_mentions_project_two_binding_provenance_rows() -> bool { + ((idb_provenances(idb_supplied_bridge()).len() as i64) == 2) +} + +pub fn witness_provenance_rows_dedupe_to_one_provider_file_dependency() -> bool { + { + let edges = idb_file_dependencies(idb_supplied_bridge()); + match ((edges.clone().len() as i64) == 1) { + false => false, + true => match edges.clone().first().cloned() { + None => false, + Some(edge) => { + (((edge.provider_file.clone() == idb_live_tree_source_path()) + && (edge.consumer_file.clone() == idb_consumer_source_path())) + && !(edge.consumer_file.clone() == edge.provider_file.clone())) + } + }, + } + } +} + +pub fn idb_withheld_control_note() -> String { + thread_local! { + static CACHED: String = { + "THE WITHHELD-PROVIDER CONTROL, asserting a typed refusal rather than an empty result. The earlier version asserted zero provenance rows and zero edges, which is satisfied equally by a REFUSAL and by a successful projection that produced nothing -- and only the first is safe for a loader. This asserts the population is ReferencePopulationRefused with an Unbound first failure, and separately that the projection is NOT Ready, so an empty success cannot pass.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_withheld_provider_refuses_as_unbound() -> bool { + { + let bridge = idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![]), + Rc::new(vec![idb_subject_name()]), + 2, + ); + (idb_population_refused_unbound(bridge.clone()) && !idb_is_ready(bridge.clone())) + } +} + +pub fn idb_fail_open_regression_receipt() -> String { + thread_local! { + static CACHED: String = { + "MEASURED, BY REINSTATING THE DEFECT. The fail-open adapter this file used to contain was put back temporarily -- filter to Bound, hand-mint AllReferencesBound from the survivors -- and exactly TWO rows red: witness_withheld_provider_refuses_as_unbound and witness_partially_bound_population_refuses_whole. Every other row, INCLUDING the 2-provenance-to-1-edge pair, passed under the defect. That is the whole argument for these two controls: the positive law never detected the fail-open and never could, because a fabricated success is still a success. They are the executing evidence that the totality property holds, so they stay enrolled rather than retiring now that the adapter is gone (DESIGN section 4b(4)).".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_partial_population_note() -> String { + thread_local! { + static CACHED: String = { + "THE PLANTED PARTIAL POPULATION -- the control that catches the fail-open this file used to contain. The consumer requires TWO cross-module type references and only ONE provider is supplied, so one reference binds and the other cannot. The whole population must refuse. Under the discarded adapter this case emitted a provider-file edge from the single successful binding and read as a complete closure.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_partially_bound_population_refuses_whole() -> bool { + { + let bridge = idb_bridge_for( + idb_partial_consumer_source_path(), + Rc::new(vec![idb_live_tree_source_path()]), + Rc::new(vec![idb_subject_name(), idb_missing_provider_name()]), + 3, + ); + ((!idb_is_ready(bridge.clone()) && ((idb_provenances(bridge.clone()).len() as i64) == 0)) + && ((idb_file_dependencies(bridge.clone()).len() as i64) == 0)) + } +} + +pub fn idb_pool_equality_note() -> String { + thread_local! { + static CACHED: String = { + "POOL INDEPENDENCE, asserted as an EQUALITY rather than as a still-passes. Adding an unrelated module to the candidate population must leave the binding identities and the edge set unchanged -- that is what distinguishes derivation from pool-membership coincidence, which is the failure mode this whole lane exists to eliminate. Asserting only that the positive still greens would not detect an edge set that grew.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_edge_key(edges: Rc>>) -> String { + edges.clone().iter().cloned().fold( + "".to_string(), + |acc: String, edge: Rc| { + ((((acc + &"|".to_string()) + &edge.consumer_file.clone()) + &">".to_string()) + + &edge.provider_file.clone()) + }, + ) +} + +pub fn idb_provenance_key(rows: Rc>>) -> String { + rows.clone().iter().cloned().fold( + "".to_string(), + |acc: String, row: Rc| { + ((((acc + &"|".to_string()) + + &(row.reference_occurrence.clone().value.clone()).to_string()) + + &">".to_string()) + + &(row.declaration_occurrence.clone().value.clone()).to_string()) + }, + ) +} + +pub fn witness_unrelated_candidate_changes_nothing() -> bool { + { + let without = idb_supplied_bridge(); + let with_unrelated = idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![ + idb_live_tree_source_path(), + idb_unrelated_source_path(), + ]), + Rc::new(vec![idb_subject_name()]), + 2, + ); + (((idb_is_ready(without.clone()) && idb_is_ready(with_unrelated.clone())) + && (idb_edge_key(idb_file_dependencies(without.clone())) + == idb_edge_key(idb_file_dependencies(with_unrelated.clone())))) + && (idb_provenance_key(idb_provenances(without.clone())) + == idb_provenance_key(idb_provenances(with_unrelated.clone())))) + } +} + +pub fn idb_same_file_note() -> String { + thread_local! { + static CACHED: String = { + "SAME-FILE RULING, exercised rather than trusted. A consumer whose provider declaration lives in its OWN file must retain its binding provenance and emit NO file self-edge -- the carrier law says so, and a projection that emitted a self-edge would make every file depend on itself and defeat the closure.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_same_file_binding_retains_provenance_without_self_edge() -> bool { + { + let bridge = idb_bridge_for( + idb_same_file_source_path(), + Rc::new(vec![]), + Rc::new(vec![idb_subject_leaf_name()]), + 2, + ); + ((idb_is_ready(bridge.clone()) && ((idb_provenances(bridge.clone()).len() as i64) == 2)) + && ((idb_file_dependencies(bridge.clone()).len() as i64) == 0)) + } +} + +pub fn idb_unknown_candidate_note() -> String { + thread_local! { + static CACHED: String = { + "A path absent from the candidate population REFUSES and is never resolved to provider contents by default. This is the control for the defaulting behaviour the candidate rows replaced: under the old path switch this call would have parsed the real provider's text under an unrelated storage path and bound successfully, which would make every later provider-discovery control meaningless.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_unknown_candidate_path_refuses() -> bool { + match (*idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![ + "dag/test/fixture/import_deletion_bridge/never_declared.dag".to_string(), + ]), + Rc::new(vec![idb_subject_leaf_name()]), + 2, + )) + .clone() + { + IdbBridge::IdbBridgeCandidateUnknown { file: f, .. } => { + (f.clone() == "dag/test/fixture/import_deletion_bridge/never_declared.dag".to_string()) + } + _ => false, + } +} + +pub fn idb_malformed_note() -> String { + thread_local! { + static CACHED: String = { + "A malformed provider source must surface as a parse refusal, not as an absent provider. Collapsing it to `no providers` would make an unparseable file indistinguishable from one that was never offered, and the remedies differ.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_malformed_provider_source_refuses() -> bool { + match (*idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![idb_malformed_source_path()]), + Rc::new(vec![idb_subject_name()]), + 2, + )) + .clone() + { + IdbBridge::IdbBridgeParseRefused { file: _, .. } => true, + _ => false, + } +} + +pub fn idb_selection_note() -> String { + thread_local! { + static CACHED: String = { + "THE SELECTION REFUSAL. Asking for a reference count the fixture does not carry must refuse rather than proceed on whatever was found -- otherwise the multiplicity law restates its input instead of testing the projection.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_reference_count_mismatch_refuses() -> bool { + match (*idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![idb_live_tree_source_path()]), + Rc::new(vec![idb_subject_name()]), + 3, + )) + .clone() + { + IdbBridge::IdbBridgeSelectionRefused { observed: n, .. } => (n.clone() == 2), + _ => false, + } +} + +pub fn idb_ambiguity_note() -> String { + thread_local! { + static CACHED: String = { + "AMBIGUITY MUST NAME THE COMPETING DECLARATIONS. Two admissible providers each declare LiveTreeDisposition, so the reference has two candidates and cannot be bound. The requirement is not that the count is two -- two ambiguity outcomes with the same cardinality over different declarations are different states with different remedies -- so this asserts the EXACT declaration identities: the two candidate terminals differ from each other, and each is a declaration of this name present in the assembled transport.\n\nThe identities are available because the carrier was repaired to keep them. resolve_reference_occurrence_binding already returned OccurrenceAmbiguous { occurrence, candidates } and the projection discarded the candidates with `candidates: _`; it now carries the upstream AmbiguousBindingCandidates itself, so this control reads production data rather than a local reconstruction.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_declaration_occurrences_named( + transport: Rc, + name: String, +) -> Rc> { + transport.declarations.clone().iter().cloned().fold( + idb_no_occurrence_ids(), + |acc: Rc>, declaration: Rc| { + match idb_index_entry_for_occurrence(transport.clone(), declaration.occurrence.clone()) + { + None => acc.clone(), + Some(entry) => match (entry.projection.clone().authored_name.clone() + == name.clone()) + { + true => { + v1_rt::concat(acc.clone(), Rc::new(vec![declaration.occurrence.clone()])) + } + false => acc.clone(), + }, + } + }, + ) +} + +pub fn idb_no_occurrence_ids() -> Rc> { + Rc::new(vec![]) +} + +pub fn idb_occurrence_in(ids: Rc>, id: OccurrenceId) -> bool { + ids.clone() + .iter() + .cloned() + .fold(false, |found: bool, candidate: OccurrenceId| { + (found || (candidate.value.clone() == id.value.clone())) + }) +} + +pub fn idb_ambiguity_candidates_for_two_providers( +) -> Option>> { + match (*idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![ + idb_live_tree_source_path(), + idb_second_provider_source_path(), + ]), + Rc::new(vec![idb_subject_name()]), + 2, + )) + .clone() + { + IdbBridge::IdbBridgeProjected { ref projection, .. } => { + let ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionBindingRefused { population: population, .. } = projection.as_ref() else { unreachable!() }; + match (*population.clone()).clone() { + BoundReferencePopulation::ReferencePopulationRefused { + first_failure: failure, + .. + } => match (*failure.clone()).clone() { + ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { + candidates, + .. + } => Some(candidates.clone()), + _ => None, + }, + _ => None, + } + } + _ => None, + } +} + +pub fn idb_two_provider_transport_declarations() -> Rc> { + match idb_closure_row_for( + idb_consumer_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ) + .first() + .cloned() + { + None => idb_no_occurrence_ids(), + Some(consumer_row) => match (*assemble_cross_file_binding_closure( + consumer_row.clone(), + v1_rt::concat( + idb_closure_row_for( + idb_live_tree_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ), + idb_closure_row_for( + idb_second_provider_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ), + ), + )) + .clone() + { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: _, + .. + } => idb_no_occurrence_ids(), + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + transport: t, + .. + } => idb_declaration_occurrences_named(t.clone(), idb_subject_leaf_name()), + }, + } +} + +pub fn witness_two_providers_refuse_as_ambiguous_naming_both_candidates() -> bool { + match idb_ambiguity_candidates_for_two_providers() { + None => false, + Some(candidates) => { + let first_id = candidates + .first + .clone() + .containment + .clone() + .terminal + .clone(); + let second_id = candidates + .second + .clone() + .containment + .clone() + .terminal + .clone(); + let declared = idb_two_provider_transport_declarations(); + (((!(first_id.value.clone() == second_id.value.clone()) + && idb_occurrence_in(declared.clone(), first_id.clone())) + && idb_occurrence_in(declared.clone(), second_id.clone())) + && ((declared.clone().len() as i64) == 2)) + } + } +} + +pub fn idb_nested_rc_pattern_emitter_defect() -> String { + thread_local! { + static CACHED: String = { + "EMITTER DEFECT, located rather than routed around silently. The ambiguity control was first authored as one nested pattern -- ReferenceDerivedDependencyProjectionBindingRefused { population: ReferencePopulationRefused { first_failure } } -- which the v1 interpreter accepts and the Rust emitter lowers to `let ... = projection.as_ref()` matching an Rc field against a bare BoundReferencePopulation pattern (E0308, emitted at src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs). So a nested coproduct pattern through a reference-counted field does not survive emission, and regen --verify reports divergence 0 while the crate does not compile: byte-fidelity is not target-language validity.\n\nIt is written here as two sequential matches, which is semantically identical and is the idiom the surrounding corpus already uses. That is NOT a repair, and it is recorded rather than left implicit because the class is the open Rc wrap-decision lane, whose whole subject is that the emitter decides Rc wrapping without a structural predicate. This control is not the place to change a load-bearing emitter.\n\nDISSOLVE-ON: the wrap-decision predicate lands and a nested pattern through an Rc field emits valid Rust; then this note deletes and the nesting may collapse back to one pattern.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_module_storage_note() -> String { + thread_local! { + static CACHED: String = { + "MODULE-TO-FILE CONTRADICTIONS MUST REFUSE, not resolve to one of the two answers. Two candidate files declare the SAME module path, so the module-to-file index has no single answer for it. That is a contradiction about storage, and either arm that catches it is a typed refusal -- what may never happen is a successful projection that silently picked a file, because the loader would then bring in one home and ignore the other.\n\nWHICH stage refuses is measured, not assumed: closure ASSEMBLY refuses, before the module-to-file index is ever built by this file, because assembly validates the accumulated rows itself. A first version of this control tolerated either that arm or an index refusal, which is a two-answer assertion -- it would have passed had the contradiction moved between the two stages, and it would not have said which stage held the line. Narrowing it to the arm that actually fires required a second repair of the same information-discarding class the ambiguity carrier had: this file was collapsing the assembly refusal to a bare marker, so the exact contradiction was unavailable to assert. It now carries AssembledCrossFileBindingClosureRefusal through, and this row names the module path and both competing files.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_conflicting_module_storage_refuses() -> bool { + { + let bridge = idb_bridge_for( + idb_consumer_source_path(), + Rc::new(vec![ + idb_live_tree_source_path(), + idb_conflicting_source_path(), + ]), + Rc::new(vec![idb_subject_name()]), + 2, + ); + match (*bridge.clone()).clone() { + IdbBridge::IdbBridgeAssemblyRefused { refusal: refusal, .. } => match (*refusal.clone()).clone() { + AssembledCrossFileBindingClosureRefusal::AssembledCrossFileBindingClosureModulePathFileRefused { refusal: index_refusal, .. } => match (*index_refusal.clone()).clone() { + ModulePathFileIndexRefusal::ModulePathFileConflict { module_path: m, first_file: a, second_file: b, .. } => ((((m.clone() == idb_provider_module_name()) && !(a.clone() == b.clone())) && ((a.clone() == idb_live_tree_source_path()) || (a.clone() == idb_conflicting_source_path()))) && ((b.clone() == idb_live_tree_source_path()) || (b.clone() == idb_conflicting_source_path()))), + _ => false, +}, +}, + _ => false, +} + } +} + +pub fn idb_missing_storage_note() -> String { + thread_local! { + static CACHED: String = { + "A MISSING module-to-file row must surface as a file-projection refusal, not as a dropped edge. This control supplies the production projection with an index from which the provider module's row has been REMOVED, so the binding succeeds and only the storage join fails -- which is exactly the state where an implementation that silently omitted the edge would under-load the closure while reporting success.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_index_without_provider_module() -> Option>> { + match idb_closure_row_for( + idb_consumer_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ) + .first() + .cloned() + { + None => None, + Some(consumer_row) => match (*assemble_cross_file_binding_closure( + consumer_row.clone(), + idb_closure_row_for( + idb_live_tree_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ), + )) + .clone() + { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: _, + .. + } => None, + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + module_files: rows, + .. + } => Some(rows.clone().iter().cloned().fold( + v1_rt::rc_empty_map::(), + |acc: Rc>, row: Rc| { + match (row.module_path.clone() == idb_provider_module_name()) { + true => acc.clone(), + false => v1_rt::rc_map_insert( + acc.clone(), + row.module_path.clone(), + row.file_path.clone(), + ), + } + }, + )), + }, + } +} + +pub fn idb_population_for_supplied_provider() -> Option> { + match idb_closure_row_for( + idb_consumer_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ) + .first() + .cloned() + { + None => None, + Some(consumer_row) => match (*assemble_cross_file_binding_closure( + consumer_row.clone(), + idb_closure_row_for( + idb_live_tree_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ), + )) + .clone() + { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: _, + .. + } => None, + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + transport: t, + inputs: i, + .. + } => match (*structural_binding_walk_selected_references( + t.clone(), + i.clone(), + idb_references_in_names(t.clone(), Rc::new(vec![idb_subject_name()])), + )) + .clone() + { + StructuralBindingWalk::StructuralBindingWalkRefused { refusal: _, .. } => None, + StructuralBindingWalk::StructuralBindingWalkReady { + population: population, + .. + } => Some(population.clone()), + }, + }, + } +} + +pub fn witness_missing_provider_module_storage_row_refuses_file_projection() -> bool { + match idb_population_for_supplied_provider() { + None => false, + Some(population) => match idb_index_without_provider_module() { + None => false, + Some(pruned) => match (*reference_derived_dependency_projection(population.clone(), pruned.clone())).clone() { + ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionFileRefused { .. } => true, + _ => false, +}, +}, +} +} + +pub fn idb_join_note() -> String { + thread_local! { + static CACHED: String = { + "ITEM 4 -- THE TYPE-BINDING JOIN, and why it is a join rather than a filter flip.\n\nClause (e) excludes TypeOccurrence for a stated reason: a parser-produced annotation such as `Int` would enter the population and refuse it whole, because std.types was never given to the closure. The wrong repairs are both available and both forbidden. Reclassifying TypeOccurrence as clause-(e)-inducing would make that clause say something its contract does not and would change every consumer of it silently. Dropping an unbindable type reference to keep the projection green would render `could not see the provider` as `nothing is depended on` -- the empty-observation narrow, worse than the widen DESIGN section 5 forbids, because a widen is merely expensive and a narrow is silently uncovered.\n\nSo the admission surface has one arm per route and the walk folds both into ONE population through one shared candidate index. The controls below assert the three facts that distinguish a join from either wrong repair: both routes reach the same population, clause (e) itself is unchanged, and an unbindable type reference refuses by NAME.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_type_consumer_source_path() -> String { + "dag/test/fixture/import_deletion_bridge/type_join_consumer.dag".to_string() +} + +pub fn idb_type_consumer_source() -> String { + (((("module test.fixture.import_deletion_bridge.type_join_consumer\n".to_string() + &"\n".to_string()) + &"fn reads_disposition(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n".to_string()) + &" d\n".to_string()) + &"}\n".to_string()) +} + +pub fn idb_joined_walk_for( + consumer_path: String, + provider_paths: Rc>, +) -> Option> { + match idb_closure_row_for( + consumer_path.clone(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ) + .first() + .cloned() + { + None => None, + Some(consumer_row) => match (*assemble_cross_file_binding_closure( + consumer_row.clone(), + provider_paths.clone().iter().cloned().fold( + idb_no_closure_rows(), + |acc: Rc>>, path: String| { + v1_rt::concat( + acc, + idb_closure_row_for( + path.clone(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ), + ) + }, + ), + )) + .clone() + { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: _, + .. + } => None, + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + transport: t, + inputs: i, + .. + } => Some(joined_dependency_binding_walk(t.clone(), i.clone())), + }, + } +} + +pub fn witness_clause_e_still_excludes_type_references() -> bool { + match (*occurrence_category_clause_e_dependency_inducing_verdict(OccurrenceCategory::TypeOccurrence)).clone() { + OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyNotInducing { category: _, .. } => true, + OccurrenceCategoryClauseEDependencyInducingVerdict::OccurrenceCategoryClauseEDependencyInducing => false, +} +} + +pub fn witness_admission_routes_each_category_exactly_once() -> bool { + ((((((idb_admits_clause_e(OccurrenceCategory::CallableOccurrence) + && idb_admits_clause_e(OccurrenceCategory::ConstructorOccurrence)) + && idb_admits_clause_e(OccurrenceCategory::NamespaceSegmentOccurrence)) + && idb_admits_clause_e(OccurrenceCategory::LexicalValueOccurrence)) + && idb_admits_type(OccurrenceCategory::TypeOccurrence)) + && idb_excludes(OccurrenceCategory::FieldOccurrence)) + && idb_excludes(OccurrenceCategory::MethodOccurrence)) +} + +pub fn idb_admits_clause_e(category: OccurrenceCategory) -> bool { + match (*reference_dependency_admission_for_category(category.clone())).clone() { + ReferenceDependencyAdmission::ReferenceDependencyAdmittedClauseE => true, + ReferenceDependencyAdmission::ReferenceDependencyAdmittedTypeReference => false, + ReferenceDependencyAdmission::ReferenceDependencyExcluded { category: _, .. } => false, + } +} + +pub fn idb_admits_type(category: OccurrenceCategory) -> bool { + match (*reference_dependency_admission_for_category(category.clone())).clone() { + ReferenceDependencyAdmission::ReferenceDependencyAdmittedTypeReference => true, + ReferenceDependencyAdmission::ReferenceDependencyAdmittedClauseE => false, + ReferenceDependencyAdmission::ReferenceDependencyExcluded { category: _, .. } => false, + } +} + +pub fn idb_excludes(category: OccurrenceCategory) -> bool { + match (*reference_dependency_admission_for_category(category.clone())).clone() { + ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: observed, .. + } => (observed.clone() == category.clone()), + ReferenceDependencyAdmission::ReferenceDependencyAdmittedClauseE => false, + ReferenceDependencyAdmission::ReferenceDependencyAdmittedTypeReference => false, + } +} + +pub fn witness_clause_e_alone_derives_no_edge_for_a_type_only_consumer() -> bool { + match idb_clause_e_edges_for(idb_type_consumer_source_path(), idb_live_tree_source_path()) { + None => false, + Some(edges) => ((edges.clone().len() as i64) == 0), + } +} + +pub fn witness_joined_walk_derives_the_type_reference_edge() -> bool { + match idb_joined_edges_for(idb_type_consumer_source_path(), idb_live_tree_source_path()) { + None => false, + Some(edges) => { + (((edges.clone().len() as i64) == 1) + && edges.clone().iter().cloned().fold( + false, + |found: bool, edge: Rc| { + (found || (edge.provider_file.clone() == idb_live_tree_source_path())) + }, + )) + } + } +} + +pub fn idb_clause_e_edges_for( + consumer_path: String, + provider_path: String, +) -> Option>>> { + match idb_closure_row_for(consumer_path.clone(), DeclarationExposureGrounding::ModuleLocalMemberExposure).first().cloned() { + None => None, + Some(consumer_row) => match (*assemble_cross_file_binding_closure(consumer_row.clone(), idb_closure_row_for(provider_path.clone(), DeclarationExposureGrounding::ModuleLocalMemberExposure))).clone() { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { refusal: _, .. } => None, + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { transport: t, inputs: i, module_files: rows, .. } => match (*module_path_file_index_from_rows(rows.clone())).clone() { + ModulePathFileIndex::ModulePathFileIndexRefused { refusal: _, .. } => None, + ModulePathFileIndex::ModulePathFileIndexReady { entries: module_files, .. } => match (*structural_binding_walk_selected_references(t.clone(), i.clone(), idb_clause_e_refs_for(consumer_path.clone()))).clone() { + StructuralBindingWalk::StructuralBindingWalkRefused { refusal: _, .. } => None, + StructuralBindingWalk::StructuralBindingWalkReady { population: population, .. } => match (*reference_derived_dependency_projection(population.clone(), module_files.clone())).clone() { + ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionReady { file_dependencies: edges, .. } => Some(edges.clone()), + _ => None, +}, +}, +}, +}, +} +} + +pub fn idb_joined_edges_for( + consumer_path: String, + provider_path: String, +) -> Option>>> { + match idb_closure_row_for(consumer_path.clone(), DeclarationExposureGrounding::ModuleLocalMemberExposure).first().cloned() { + None => None, + Some(consumer_row) => match (*assemble_cross_file_binding_closure(consumer_row.clone(), idb_closure_row_for(provider_path.clone(), DeclarationExposureGrounding::ModuleLocalMemberExposure))).clone() { + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { refusal: _, .. } => None, + AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { transport: t, inputs: i, module_files: rows, .. } => match (*module_path_file_index_from_rows(rows.clone())).clone() { + ModulePathFileIndex::ModulePathFileIndexRefused { refusal: _, .. } => None, + ModulePathFileIndex::ModulePathFileIndexReady { entries: module_files, .. } => match (*joined_dependency_binding_walk(t.clone(), i.clone())).clone() { + StructuralBindingWalk::StructuralBindingWalkRefused { refusal: _, .. } => None, + StructuralBindingWalk::StructuralBindingWalkReady { population: population, .. } => match (*reference_derived_dependency_projection(population.clone(), module_files.clone())).clone() { + ReferenceDerivedDependencyProjection::ReferenceDerivedDependencyProjectionReady { file_dependencies: edges, .. } => Some(edges.clone()), + _ => None, +}, +}, +}, +}, +} +} + +pub fn witness_both_routes_reach_one_population() -> bool { + match idb_joined_walk_for( + idb_consumer_source_path(), + Rc::new(vec![idb_live_tree_source_path()]), + ) { + None => false, + Some(walk) => match (*walk.clone()).clone() { + StructuralBindingWalk::StructuralBindingWalkRefused { refusal: _, .. } => false, + StructuralBindingWalk::StructuralBindingWalkReady { + population: population, + .. + } => match (*population.clone()).clone() { + BoundReferencePopulation::AllReferencesBound { + providers: providers, + .. + } => { + (((providers.clone().len() as i64) == 3) + && ((idb_clause_e_refs_for(idb_consumer_source_path()).len() as i64) == 1)) + } + BoundReferencePopulation::ReferencePopulationRefused { .. } => false, + }, + }, + } +} + +pub fn idb_demand_note() -> String { + thread_local! { + static CACHED: String = { + "THE DEMAND, which is the whole reason the production switch waits. A type reference whose provider was never supplied must refuse and must NAME the occurrence it could not bind -- that named occurrence IS the loader's work list. A skip would leave the projection green and the closure short, with the deficit's frequency zeroed by construction so it never ranks for fixing.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_unsupplied_type_reference_refuses_by_name() -> bool { + match idb_joined_walk_for(idb_type_consumer_source_path(), idb_no_strings()) { + None => false, + Some(walk) => match (*walk.clone()).clone() { + StructuralBindingWalk::StructuralBindingWalkRefused { refusal: _, .. } => false, + StructuralBindingWalk::StructuralBindingWalkReady { + population: population, + .. + } => match (*population.clone()).clone() { + BoundReferencePopulation::AllReferencesBound { providers: _, .. } => false, + BoundReferencePopulation::ReferencePopulationRefused { + first_failure: failure, + .. + } => match (*failure.clone()).clone() { + ReferenceBindingProjection::ReferenceBindingProjectionUnbound { + occurrence: occurrence, + .. + } => idb_named_occurrence_is( + idb_type_consumer_source_path(), + occurrence.clone(), + idb_subject_name(), + ), + _ => false, + }, + }, + }, + } +} + +pub fn idb_no_strings() -> Rc> { + Rc::new(vec![]) +} + +pub fn idb_named_occurrence_is( + consumer_path: String, + occurrence: OccurrenceId, + name: String, +) -> bool { + match idb_parsed_row(consumer_path.clone()) { + None => false, + Some(row) => match (*row.clone()).clone() { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => false, + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport: t, + .. + } => match idb_index_entry_for_occurrence(t.clone(), occurrence.clone()) { + None => false, + Some(entry) => (entry.projection.clone().authored_name.clone() == name.clone()), + }, + }, + } +} + +pub fn idb_clause_e_refs_for(path: String) -> Rc>> { + match idb_parsed_row(path.clone()) { + None => idb_no_references(), + Some(row) => match (*row.clone()).clone() { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => { + idb_no_references() + } + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport: t, + .. + } => t.references.clone().iter().cloned().fold( + idb_no_references(), + |acc: Rc>>, r: Rc| { + match (*reference_dependency_admission_for_category(r.category.clone())).clone() + { + ReferenceDependencyAdmission::ReferenceDependencyAdmittedClauseE => { + v1_rt::concat(acc.clone(), Rc::new(vec![r.clone()])) + } + ReferenceDependencyAdmission::ReferenceDependencyAdmittedTypeReference => { + acc.clone() + } + ReferenceDependencyAdmission::ReferenceDependencyExcluded { + category: _, + .. + } => acc.clone(), + } + }, + ), + }, + } +} + +pub fn idb_all_refs_for(path: String) -> Rc>> { + match idb_parsed_row(path.clone()) { + None => idb_no_references(), + Some(row) => match (*row.clone()).clone() { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => { + idb_no_references() + } + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport: t, + .. + } => t.references.clone(), + }, + } +} + +pub fn idb_reference_path_controls_note() -> String { + thread_local! { + static CACHED: String = { + "REFERENCE-PATH CONTROLS, on real parsed source rather than hand-built values. Each asserts a DIFFERENT decision of resolve_reference_path, which is what keeps them from being one claim written four ways: a path whose head and terminal both bind resolves to the TERMINAL declaration (not the module head, so the assertion names the terminal's own authored spelling); a path whose head binds and whose segment names nothing refuses AT THE SEGMENT, which is the discriminating red for the head-only widen this carrier deletes -- under that widen this case produced a ready dependency; a path whose head names nothing refuses at the HEAD, before any descent, so head and segment failures are distinguishable rather than one Unbound; and a head naming two modules refuses as head ambiguity BEFORE descent, so a nearest-match never gets the chance to pick. The resolved case is the positive control the three refusals are read against: without it, four refusals would be satisfiable by a resolver that refuses everything.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_reference_path_closure() -> Option> { + idb_reference_path_closure_with(Rc::new(vec![idb_live_tree_source_path()])) +} + +pub fn idb_reference_path_provider_choice_note() -> String { + thread_local! { + static CACHED: String = { + "The provider SET is a parameter because two of these controls are about cardinality, and cardinality is a property of the assembled population rather than of the reference. Head ambiguity needs two modules whose last segment is live_tree; segment ambiguity needs one module declaring the member twice. Neither is expressible by changing the consumer text, which is why the closure takes its providers rather than naming one.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_reference_path_closure_with( + providers: Rc>, +) -> Option> { + match idb_closure_row_for( + idb_path_consumer_source_path(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ) + .first() + .cloned() + { + None => None, + Some(consumer_row) => Some(assemble_cross_file_binding_closure( + consumer_row.clone(), + providers.clone().iter().cloned().fold( + idb_no_closure_rows(), + |acc: Rc>>, path: String| { + v1_rt::concat( + acc, + idb_closure_row_for( + path.clone(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, + ), + ) + }, + ), + )), + } +} + +pub fn idb_reference_path_resolution_for(name: String) -> Option> { + idb_reference_path_resolution_in(name.clone(), Rc::new(vec![idb_live_tree_source_path()])) +} + +pub fn idb_reference_path_resolution_in( + name: String, + providers: Rc>, +) -> Option> { + match idb_reference_path_closure_with(providers.clone()) + .as_deref() + .cloned() + { + None => None, + Some(AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: _, + .. + }) => None, + Some(AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + transport: t, + inputs: i, + .. + }) => match (*occurrence_candidate_index_build(t.clone(), i.clone())).clone() { + OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexReady { + index: index, .. + } => t.references.clone().iter().cloned().fold( + None, + |found: _, r: Rc| match found.clone() { + Some(_) => found.clone(), + None => match v1_rt::map_get( + &index.entries_by_id.clone(), + r.occurrence.clone().value.clone(), + ) { + None => None, + Some(entry) => match (entry.projection.clone().authored_name.clone() + == name.clone()) + { + false => None, + true => { + Some(resolve_reference_path(t.clone(), index.clone(), r.clone())) + } + }, + }, + }, + ), + _ => None, + }, + } +} + +pub fn idb_resolved_terminal_name(name: String) -> Option { + match idb_reference_path_resolution_for(name.clone()) + .as_deref() + .cloned() + { + None => None, + Some(ReferencePathResolution::ReferencePathResolved { + terminal: terminal, .. + }) => match idb_reference_path_authored_name_of(terminal.occurrence.clone()) { + None => None, + Some(spelled) => Some(spelled.clone()), + }, + Some(_) => None, + } +} + +pub fn idb_reference_path_authored_name_of(occurrence: OccurrenceId) -> Option { + match idb_reference_path_closure().as_deref().cloned() { + None => None, + Some(AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureRefused { + refusal: _, + .. + }) => None, + Some(AssembledCrossFileBindingClosure::AssembledCrossFileBindingClosureReady { + transport: t, + .. + }) => match idb_index_entry_for_occurrence(t.clone(), occurrence.clone()) { + None => None, + Some(entry) => Some(entry.projection.clone().authored_name.clone()), + }, + } +} + +pub fn reference_path_resolves_to_the_terminal_declaration() -> bool { + match idb_resolved_terminal_name(idb_subject_name()) { + None => false, + Some(spelled) => (spelled.clone() == idb_subject_leaf_name()), + } +} + +pub fn reference_path_missing_member_refuses_at_the_segment() -> bool { + match idb_reference_path_resolution_for("live_tree.NoSuchMember".to_string()) + .as_deref() + .cloned() + { + Some(ReferencePathResolution::ReferencePathSegmentUnbound { segment, .. }) => { + (segment.name.clone() == "NoSuchMember".to_string()) + } + _ => false, + } +} + +pub fn reference_path_missing_head_refuses_before_descent() -> bool { + match idb_reference_path_resolution_for("no_such_module.Anything".to_string()) + .as_deref() + .cloned() + { + Some(ReferencePathResolution::ReferencePathHeadUnbound { + head_name: head, .. + }) => (head.clone() == "no_such_module".to_string()), + _ => false, + } +} + +pub fn idb_duplicate_member_source_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/import_deletion_bridge/duplicate_member_live_tree.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_duplicate_member_source_note() -> String { + thread_local! { + static CACHED: String = { + "One module declaring the SAME member spelling twice. This is not a well-formed program and is not meant to be: the subject is what the path resolver does when a segment has two candidates, and the answer must be a refusal naming both rather than a first-wins pick. Authored as its own fixture so no well-formed provider has to be corrupted to ask the question.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn idb_duplicate_member_source() -> String { + "module v2.std.duplicated\n\ntype Twice\n = TwiceOne\n | TwiceTwo\n\ntype Twice\n = TwiceThree\n | TwiceFour\n".to_string() +} + +pub fn reference_path_two_modules_named_alike_refuse_at_the_head() -> bool { + match idb_reference_path_resolution_in( + idb_subject_name(), + Rc::new(vec![ + idb_live_tree_source_path(), + idb_second_provider_source_path(), + ]), + ) + .as_deref() + .cloned() + { + Some(ReferencePathResolution::ReferencePathHeadAmbiguous { + head_name: head, + candidates, + .. + }) => { + ((head.clone() == "live_tree".to_string()) && ((candidates.clone().len() as i64) == 2)) + } + _ => false, + } +} + +pub fn reference_path_ordinary_record_field_is_not_a_namespace_projection() -> bool { + match idb_reference_path_resolution_for("ordinary_field".to_string()) + .as_deref() + .cloned() + { + Some(ReferencePathResolution::ReferencePathNotANamespaceProjection { + head: head, .. + }) => match head.category.clone() { + OccurrenceCategory::NamespaceSegmentOccurrence => false, + _ => true, + }, + _ => false, + } +} + +pub fn reference_path_duplicated_member_refuses_at_the_segment() -> bool { + match idb_reference_path_resolution_in( + "duplicated.Twice".to_string(), + Rc::new(vec![ + idb_live_tree_source_path(), + idb_duplicate_member_source_path(), + ]), + ) + .as_deref() + .cloned() + { + Some(ReferencePathResolution::ReferencePathSegmentAmbiguous { + segment, + candidates, + .. + }) => { + ((segment.name.clone() == "Twice".to_string()) + && ((candidates.clone().len() as i64) == 2)) + } + _ => false, + } +} + +pub fn idb_multi_segment_note() -> String { + thread_local! { + static CACHED: String = { + "MULTI-SEGMENT DESCENT, and the shape that makes it constructible from ordinary .dag source. Modules are flat per file, so a.b.c cannot be module-inside-module; but a module member CAN have members -- live_tree.LiveTreeDisposition.ReadsLiveTree is module, then type, then variant -- so three segments is two real containment steps rather than a synthetic nesting. The fully qualified spelling is asserted beside it because both are answered by ONE rule, the longest prefix naming a module: the exact-path match serves v2.std.live_tree and the suffix match serves live_tree, so neither spelling needs its own resolution route.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn reference_path_descends_three_segments_to_the_variant() -> bool { + match idb_resolved_terminal_name("live_tree.LiveTreeDisposition.ReadsLiveTree".to_string()) { + Some(spelled) => (spelled.clone() == "ReadsLiveTree".to_string()), + None => false, + } +} + +pub fn reference_path_resolves_the_fully_qualified_spelling() -> bool { + match idb_resolved_terminal_name("v2.std.live_tree.LiveTreeDisposition".to_string()) { + Some(spelled) => (spelled.clone() == idb_subject_leaf_name()), + None => false, + } +} diff --git a/src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs b/src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs index 41d3788ade7..25af15a9793 100644 --- a/src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs +++ b/src/v1/stage0/src/v1_tests_claim_occurrence_binding_parser_walk_witness_test.rs @@ -8,6 +8,7 @@ use crate::std_dissolution::DissolutionCondition::*; use crate::std_occurrence_binding_candidates::AuthoredOrderIndexRefusal::MissingAuthoredOrderRow; use crate::std_occurrence_binding_candidates::BoundReferencePopulation::AllReferencesBound; use crate::std_occurrence_binding_candidates::DeclarationExposure::LexicalExposure; +use crate::std_occurrence_binding_candidates::DeclarationExposureGrounding::ModuleLocalMemberExposure; use crate::std_occurrence_binding_candidates::DeclarationExposureIndexRefusal::MissingDeclarationExposure; use crate::std_occurrence_binding_candidates::DirectModuleDependencyBuild::DirectModuleDependencyListReady; use crate::std_occurrence_binding_candidates::OccurrenceCandidateIndexBuild::OccurrenceCandidateIndexReady; @@ -29,9 +30,10 @@ pub use crate::std_occurrence_binding_candidates::{ }; pub use crate::std_occurrence_binding_candidates::{ AuthoredOrderIndexRefusal, AuthoredOrderRow, BoundReferencePopulation, BoundReferenceProvider, - DeclarationExposure, DeclarationExposureIndexRefusal, DeclarationExposureRow, - DirectModuleDependencyBuild, OccurrenceBindingCandidateInputs, OccurrenceCandidateIndexBuild, - ReferenceBindingProjection, StructuralBindingIndexRefusal, StructuralBindingWalk, + DeclarationExposure, DeclarationExposureGrounding, DeclarationExposureIndexRefusal, + DeclarationExposureRow, DirectModuleDependencyBuild, OccurrenceBindingCandidateInputs, + OccurrenceCandidateIndexBuild, ReferenceBindingProjection, StructuralBindingIndexRefusal, + StructuralBindingWalk, }; pub use crate::std_occurrence_identity::{ AuthoredTokenOrdinal, DeclarationOccurrence, OccurrenceId, OccurrenceIndexEntry, @@ -122,6 +124,7 @@ pub fn obcpw_parse(file: String, source: String) -> Rc { inputs: occurrence_binding_inputs_from_transport( module_path.clone(), transport.clone(), + DeclarationExposureGrounding::ModuleLocalMemberExposure, ), }), } diff --git a/src/v1/stage0/src/v1_tests_claim_parser_declaration_category_matrix_witness_test.rs b/src/v1/stage0/src/v1_tests_claim_parser_declaration_category_matrix_witness_test.rs new file mode 100644 index 00000000000..2f8ef139007 --- /dev/null +++ b/src/v1/stage0/src/v1_tests_claim_parser_declaration_category_matrix_witness_test.rs @@ -0,0 +1,213 @@ +// Generated by v1 compiler -- do not edit. +// Source module: v1.tests.claim.parser_declaration_category_matrix_witness_test + +pub use crate::std_occurrence_identity::DeclarationOccurrence; +pub use crate::std_types::Bool; +use crate::std_types::Bool::*; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +pub use crate::v1_tests_claim_parser_declaration_matrix_support::PdcTransport; +use crate::v1_tests_claim_parser_declaration_matrix_support::PdcTransport::{ + PdcTransportReady, PdcTransportRefused, +}; +pub use crate::v1_tests_claim_parser_declaration_matrix_support::{ + pdc_declares_nothing, pdc_identity_row, pdc_index_entry_for_declaration, + pdc_matrix_category_count, pdc_non_type_category_count, pdc_parse_matrix, pdc_reference_row, + pdc_row, pdc_zero_row, +}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +pub fn pdc_matrix_witness_note() -> String { + thread_local! { + static CACHED: String = { + "Parser declaration-category denominator. Pins, per authored declaration form, the OccurrenceCategory population the v1 parser mints — including the forms that mint nothing. Observation-only: no resolution, exposure, or binding claim. The zero rows are the load-bearing half; minting a category later must edit this file.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn pdc_offline_recipe() -> String { + thread_local! { + static CACHED: String = { + "OFFLINE LOCAL RECIPE (requires v1 parser pool): target/release/claim_batch --roster-from-discovery --source-root dag --source-root src/v1 --scan-dir src/v1/tests/claim. The function list is deliberately NOT enumerated here: this file is enrolled file-grain, so the empty-function authority expands it, and a hand-copied list of test fn names beside a file that already names them is a second representation that rots on the next rename.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn pdc_dissolve_on() -> String { + thread_local! { + static CACHED: String = { + "DISSOLVE-ON: witness_layer_roots admits src/v1 for parser-transport witnesses, or the execution corpus carries per-entry source roots — then this recipe deletes and discovery enrollment suffices.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn witness_matrix_fixture_parses() -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => { + (((transport.declarations.clone().len() as i64) > 0) + && ((transport.references.clone().len() as i64) > 0)) + } + } +} + +pub fn witness_function_declaration_mints_callable() -> bool { + pdc_row( + "pdc_function".to_string(), + "|CallableOccurrence".to_string(), + ) +} + +pub fn witness_second_function_declaration_mints_callable() -> bool { + pdc_row("pdc_matcher".to_string(), "|CallableOccurrence".to_string()) +} + +pub fn witness_function_parameter_binds() -> bool { + pdc_row( + "pdc_parameter".to_string(), + "|LexicalValueOccurrence".to_string(), + ) +} + +pub fn witness_match_scrutinee_parameter_binds() -> bool { + pdc_row( + "pdc_scrutinee".to_string(), + "|LexicalValueOccurrence".to_string(), + ) +} + +pub fn witness_let_binder_binds() -> bool { + pdc_row( + "pdc_let_binder".to_string(), + "|LexicalValueOccurrence".to_string(), + ) +} + +pub fn witness_pattern_binder_binds() -> bool { + pdc_row( + "pdc_pattern_binder".to_string(), + "|LexicalValueOccurrence".to_string(), + ) +} + +pub fn witness_product_type_declaration_is_declared() -> bool { + pdc_row("PdcProduct".to_string(), "|TypeOccurrence".to_string()) +} + +pub fn witness_coproduct_type_declaration_is_declared() -> bool { + pdc_row("PdcSum".to_string(), "|TypeOccurrence".to_string()) +} + +pub fn witness_nullary_constructor_is_declared() -> bool { + pdc_row( + "PdcNullary".to_string(), + "|ConstructorOccurrence".to_string(), + ) +} + +pub fn witness_payload_constructor_is_declared() -> bool { + pdc_row( + "PdcPayload".to_string(), + "|ConstructorOccurrence".to_string(), + ) +} + +pub fn witness_product_field_is_declared() -> bool { + pdc_row( + "pdc_product_field".to_string(), + "|FieldOccurrence".to_string(), + ) +} + +pub fn witness_coproduct_payload_field_is_declared_at_its_declaration_site() -> bool { + pdc_identity_row( + "pdc_payload_field".to_string(), + "|pdc_payload_field#FieldOccurrence#span:109-126#path:>app.pdc_matrix>PdcSum>PdcPayload" + .to_string(), + ) +} + +pub fn witness_pattern_field_position_is_not_a_declaration() -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => transport.declarations.clone().iter().cloned().fold( + true, + |acc: bool, declaration: Rc| { + match pdc_index_entry_for_declaration(transport.clone(), declaration.clone()) { + None => acc.clone(), + Some(entry) => { + (acc.clone() + && (entry + .projection + .clone() + .diagnostic_span + .clone() + .start + .clone() + != 394)) + } + } + }, + ), + } +} + +pub fn witness_a_declaration_exists_at_the_coproduct_field_site() -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => transport.declarations.clone().iter().cloned().fold( + false, + |found: bool, declaration: Rc| { + match pdc_index_entry_for_declaration(transport.clone(), declaration.clone()) { + None => found.clone(), + Some(entry) => { + (found.clone() + || (entry + .projection + .clone() + .diagnostic_span + .clone() + .start + .clone() + == 109)) + } + } + }, + ), + } +} + +pub fn witness_matrix_declares_exactly_its_three_named_types() -> bool { + (pdc_matrix_category_count("TypeOccurrence".to_string()) == 3) +} + +pub fn witness_type_alias_declaration_is_declared() -> bool { + pdc_row("PdcAlias".to_string(), "|TypeOccurrence".to_string()) +} + +pub fn witness_pattern_field_position_is_a_field_reference() -> bool { + pdc_reference_row("pdc_payload_field".to_string(), "|pdc_payload_field#FieldOccurrence#span:394-411#path:>app.pdc_matrix>pdc_matcher>>".to_string()) +} + +pub fn witness_no_non_type_item_form_becomes_a_type_declaration() -> bool { + (pdc_non_type_category_count("TypeOccurrence".to_string()) == 0) +} + +pub fn witness_non_type_item_fixture_declares_its_callables() -> bool { + (pdc_non_type_category_count("CallableOccurrence".to_string()) == 2) +} diff --git a/src/v1/stage0/src/v1_tests_claim_parser_declaration_matrix_support.rs b/src/v1/stage0/src/v1_tests_claim_parser_declaration_matrix_support.rs new file mode 100644 index 00000000000..58088f625c7 --- /dev/null +++ b/src/v1/stage0/src/v1_tests_claim_parser_declaration_matrix_support.rs @@ -0,0 +1,503 @@ +// Generated by v1 compiler -- do not edit. +// Source module: v1.tests.claim.parser_declaration_matrix_support + +use self::PdcTransport::*; +use crate::std_occurrence_identity::OccurrenceCategory::{ + CallableOccurrence, ConstructorOccurrence, FieldOccurrence, LexicalValueOccurrence, + MethodOccurrence, ModuleValueOccurrence, NamespaceSegmentOccurrence, TypeOccurrence, +}; +pub use crate::std_occurrence_identity::{ + DeclarationOccurrence, OccurrenceCategory, OccurrenceId, OccurrenceIndexEntry, + OccurrenceTransport, ReferenceOccurrence, +}; +use crate::std_types::Bool::*; +pub use crate::std_types::{Bool, List, NonEmptyStr}; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::parse_authored_occurrence_binding_source; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource; +use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource::{ + ParsedOccurrenceBindingSourceReady, ParsedOccurrenceBindingSourceRefused, +}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +pub fn pdc_support_authority_note() -> String { + thread_local! { + static CACHED: String = { + "Single support authority for the parser declaration-category matrix, imported by both the enrolled green witness and its expected-red sibling. Owns the fixture source, the transport parse, the category projection, and the exact-identity rendering (authored name, category, byte span, containment chain). Declares no test fn and asserts nothing: the requirements live in the witness files, the instrument lives here once.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn pdc_matrix_source() -> String { + (((((((((((((((((((((((((("module app.pdc_matrix\n".to_string() + + &"\n".to_string()) + + &"type PdcProduct {\n".to_string()) + + &" pdc_product_field: Int\n".to_string()) + + &"}\n".to_string()) + + &"\n".to_string()) + + &"type PdcSum = PdcNullary | PdcPayload { pdc_payload_field: Int }\n" + .to_string()) + + &"\n".to_string()) + + &"data pdc_annotated_datum: Int = 7\n".to_string()) + + &"\n".to_string()) + + &"fn pdc_function(pdc_parameter: Int) -> Int {\n".to_string()) + + &" let pdc_let_binder = pdc_parameter\n".to_string()) + + &" pdc_let_binder\n".to_string()) + + &"}\n".to_string()) + + &"\n".to_string()) + + &"fn pdc_matcher(pdc_scrutinee: PdcSum) -> Int {\n".to_string()) + + &" match pdc_scrutinee {\n".to_string()) + + &" PdcNullary => {\n".to_string()) + + &" 0\n".to_string()) + + &" }\n".to_string()) + + &" PdcPayload { pdc_payload_field: pdc_pattern_binder } => {\n".to_string()) + + &" pdc_pattern_binder\n".to_string()) + + &" }\n".to_string()) + + &" }\n".to_string()) + + &"}\n".to_string()) + + &"\n".to_string()) + + &"type PdcAlias = PdcProduct\n".to_string()) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum PdcTransport { + PdcTransportReady { transport: Rc }, + PdcTransportRefused, +} +impl PdcTransport { + pub fn transport(&self) -> Rc { + match self { + PdcTransport::PdcTransportReady { + transport: __val, .. + } => __val.clone(), + PdcTransport::PdcTransportRefused => panic!("no transport on unit variant"), + } + } +} + +pub fn pdc_parse_matrix() -> Rc { + match (*parse_authored_occurrence_binding_source( + "app/pdc_matrix.dag".to_string(), + pdc_matrix_source(), + )) + .clone() + { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => { + Rc::new(PdcTransport::PdcTransportRefused) + } + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { transport, .. } => { + Rc::new(PdcTransport::PdcTransportReady { + transport: transport.clone(), + }) + } + } +} + +pub fn pdc_category_tag(category: OccurrenceCategory) -> String { + match category.clone() { + OccurrenceCategory::LexicalValueOccurrence => "LexicalValueOccurrence".to_string(), + OccurrenceCategory::TypeOccurrence => "TypeOccurrence".to_string(), + OccurrenceCategory::CallableOccurrence => "CallableOccurrence".to_string(), + OccurrenceCategory::ConstructorOccurrence => "ConstructorOccurrence".to_string(), + OccurrenceCategory::NamespaceSegmentOccurrence => "NamespaceSegmentOccurrence".to_string(), + OccurrenceCategory::FieldOccurrence => "FieldOccurrence".to_string(), + OccurrenceCategory::MethodOccurrence => "MethodOccurrence".to_string(), + OccurrenceCategory::ModuleValueOccurrence => "ModuleValueOccurrence".to_string(), + } +} + +pub fn pdc_index_entry_for_declaration( + transport: Rc, + declaration: Rc, +) -> Option> { + transport + .index + .clone() + .entries + .clone() + .iter() + .cloned() + .fold( + None, + |found: _, entry: Rc| match found.clone() { + Some(_) => found.clone(), + None => match (entry.projection.clone().occurrence.clone().value.clone() + == declaration.occurrence.clone().value.clone()) + { + true => Some(entry.clone()), + false => None, + }, + }, + ) +} + +pub fn pdc_declared_tags_for_name( + transport: Rc, + name: String, +) -> Rc> { + transport.declarations.clone().iter().cloned().fold( + Rc::new(vec![]), + |acc: Rc>, declaration: Rc| { + match pdc_index_entry_for_declaration(transport.clone(), declaration.clone()) { + None => acc.clone(), + Some(entry) => { + match (entry.projection.clone().authored_name.clone() == name.clone()) { + true => v1_rt::concat( + acc.clone(), + Rc::new(vec![pdc_category_tag(declaration.category.clone())]), + ), + false => acc.clone(), + } + } + } + }, + ) +} + +pub fn pdc_render_tags(tags: Rc>) -> String { + tags.clone() + .iter() + .cloned() + .fold("".to_string(), |acc: String, tag: String| { + ((acc + &"|".to_string()) + &tag.clone()) + }) +} + +pub fn pdc_containment_chain( + transport: Rc, + declaration: Rc, +) -> String { + declaration + .containment + .clone() + .ancestors + .clone() + .iter() + .cloned() + .fold("".to_string(), |chain: String, ancestor: OccurrenceId| { + ((chain + &">".to_string()) + + &pdc_named_or_anon(transport.clone(), ancestor.value.clone())) + }) +} + +pub fn pdc_named_or_anon(transport: Rc, occurrence_value: i64) -> String { + { + let found = pdc_name_for_occurrence(transport.clone(), occurrence_value.clone()); + match (found.clone() == "".to_string()) { + true => "".to_string(), + false => found.clone(), + } + } +} + +pub fn pdc_name_for_occurrence( + transport: Rc, + occurrence_value: i64, +) -> String { + transport + .index + .clone() + .entries + .clone() + .iter() + .cloned() + .fold( + "".to_string(), + |found: String, entry: Rc| match (found.clone() + == "".to_string()) + { + false => found.clone(), + true => match (entry.projection.clone().occurrence.clone().value.clone() + == occurrence_value.clone()) + { + true => entry.projection.clone().authored_name.clone(), + false => found.clone(), + }, + }, + ) +} + +pub fn pdc_render_occurrence( + transport: Rc, + declaration: Rc, + entry: Rc, +) -> String { + ((((((((entry.projection.clone().authored_name.clone() + &"#".to_string()) + + &pdc_category_tag(declaration.category.clone())) + + &"#span:".to_string()) + + &(entry + .projection + .clone() + .diagnostic_span + .clone() + .start + .clone()) + .to_string()) + + &"-".to_string()) + + &(entry.projection.clone().diagnostic_span.clone().end.clone()).to_string()) + + &"#path:".to_string()) + + &pdc_containment_chain(transport.clone(), declaration.clone())) +} + +pub fn pdc_identity_rows_for_name( + transport: Rc, + name: String, +) -> Rc> { + transport.declarations.clone().iter().cloned().fold( + Rc::new(vec![]), + |acc: Rc>, declaration: Rc| { + match pdc_index_entry_for_declaration(transport.clone(), declaration.clone()) { + None => acc.clone(), + Some(entry) => { + match (entry.projection.clone().authored_name.clone() == name.clone()) { + true => v1_rt::concat( + acc.clone(), + Rc::new(vec![pdc_render_occurrence( + transport.clone(), + declaration.clone(), + entry.clone(), + )]), + ), + false => acc.clone(), + } + } + } + }, + ) +} + +pub fn pdc_identity_population(transport: Rc, name: String) -> String { + pdc_render_tags(pdc_identity_rows_for_name(transport.clone(), name.clone())) +} + +pub fn pdc_declares_exactly( + transport: Rc, + name: String, + expected: String, +) -> bool { + (pdc_render_tags(pdc_declared_tags_for_name(transport.clone(), name.clone())) + == expected.clone()) +} + +pub fn pdc_reference_rows_for_name( + transport: Rc, + name: String, +) -> Rc> { + transport.references.clone().iter().cloned().fold( + Rc::new(vec![]), + |acc: Rc>, reference: Rc| { + match pdc_index_entry_for_occurrence( + transport.clone(), + reference.occurrence.clone().value.clone(), + ) { + None => acc.clone(), + Some(entry) => { + match (entry.projection.clone().authored_name.clone() == name.clone()) { + true => v1_rt::concat( + acc.clone(), + Rc::new(vec![ + ((((((((entry.projection.clone().authored_name.clone() + + &"#".to_string()) + + &pdc_category_tag(reference.category.clone())) + + &"#span:".to_string()) + + &(entry + .projection + .clone() + .diagnostic_span + .clone() + .start + .clone()) + .to_string()) + + &"-".to_string()) + + &(entry + .projection + .clone() + .diagnostic_span + .clone() + .end + .clone()) + .to_string()) + + &"#path:".to_string()) + + &pdc_reference_chain(transport.clone(), reference.clone())), + ]), + ), + false => acc.clone(), + } + } + } + }, + ) +} + +pub fn pdc_reference_chain( + transport: Rc, + reference: Rc, +) -> String { + reference + .containment + .clone() + .ancestors + .clone() + .iter() + .cloned() + .fold("".to_string(), |chain: String, ancestor: OccurrenceId| { + ((chain + &">".to_string()) + + &pdc_named_or_anon(transport.clone(), ancestor.value.clone())) + }) +} + +pub fn pdc_index_entry_for_occurrence( + transport: Rc, + occurrence_value: i64, +) -> Option> { + transport + .index + .clone() + .entries + .clone() + .iter() + .cloned() + .fold( + None, + |found: _, entry: Rc| match found.clone() { + Some(_) => found.clone(), + None => match (entry.projection.clone().occurrence.clone().value.clone() + == occurrence_value.clone()) + { + true => Some(entry.clone()), + false => None, + }, + }, + ) +} + +pub fn pdc_reference_population(transport: Rc, name: String) -> String { + pdc_render_tags(pdc_reference_rows_for_name(transport.clone(), name.clone())) +} + +pub fn pdc_reference_row(name: String, expected: String) -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => (pdc_reference_population(transport.clone(), name.clone()) == expected.clone()), + } +} + +pub fn pdc_declares_nothing(transport: Rc, name: String) -> bool { + ((pdc_declared_tags_for_name(transport.clone(), name.clone()).len() as i64) == 0) +} + +pub fn pdc_row(name: String, expected: String) -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => pdc_declares_exactly(transport.clone(), name.clone(), expected.clone()), + } +} + +pub fn pdc_zero_row(name: String) -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => pdc_declares_nothing(transport.clone(), name.clone()), + } +} + +pub fn pdc_identity_row(name: String, expected: String) -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => (pdc_identity_population(transport.clone(), name.clone()) == expected.clone()), + } +} + +pub fn pdc_non_type_item_source() -> String { + (((((((((("module app.pdc_non_type\n".to_string() + &"\n".to_string()) + + &"data pdc_nt_datum: Int = 7\n".to_string()) + + &"\n".to_string()) + + &"fn pdc_nt_fn(pdc_nt_param: Int) -> Int {\n".to_string()) + + &" pdc_nt_param\n".to_string()) + + &"}\n".to_string()) + + &"\n".to_string()) + + &"func pdc_nt_func(pdc_nt_func_param: Int) -> Int {\n".to_string()) + + &" pdc_nt_func_param\n".to_string()) + + &"}\n".to_string()) +} + +pub fn pdc_parse_non_type_items() -> Rc { + match (*parse_authored_occurrence_binding_source( + "app/pdc_non_type.dag".to_string(), + pdc_non_type_item_source(), + )) + .clone() + { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => { + Rc::new(PdcTransport::PdcTransportRefused) + } + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { transport, .. } => { + Rc::new(PdcTransport::PdcTransportReady { + transport: transport.clone(), + }) + } + } +} + +pub fn pdc_declared_category_count(transport: Rc, tag: String) -> i64 { + transport.declarations.clone().iter().cloned().fold( + 0, + |acc: i64, declaration: Rc| match (pdc_category_tag( + declaration.category.clone(), + ) == tag.clone()) + { + true => (acc.clone() + 1), + false => acc.clone(), + }, + ) +} + +pub fn pdc_matrix_category_count(tag: String) -> i64 { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => (0 - 1), + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => pdc_declared_category_count(transport.clone(), tag.clone()), + } +} + +pub fn pdc_non_type_category_count(tag: String) -> i64 { + match (*pdc_parse_non_type_items()).clone() { + PdcTransport::PdcTransportRefused => (0 - 1), + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => pdc_declared_category_count(transport.clone(), tag.clone()), + } +} + +pub fn pdc_declaration_count_row(name: String, expected: i64) -> bool { + match (*pdc_parse_matrix()).clone() { + PdcTransport::PdcTransportRefused => false, + PdcTransport::PdcTransportReady { + transport: transport, + .. + } => { + ((pdc_declared_tags_for_name(transport.clone(), name.clone()).len() as i64) + == expected.clone()) + } + } +} diff --git a/src/v1/stage0/src/v1_tests_claim_reference_derived_closure_witness_test.rs b/src/v1/stage0/src/v1_tests_claim_reference_derived_closure_witness_test.rs new file mode 100644 index 00000000000..e295a446970 --- /dev/null +++ b/src/v1/stage0/src/v1_tests_claim_reference_derived_closure_witness_test.rs @@ -0,0 +1,597 @@ +// Generated by v1 compiler -- do not edit. +// Source module: v1.tests.claim.reference_derived_closure_witness_test + +pub use crate::std_occurrence_binding_candidates::cross_file_binding_closure_row; +use crate::std_occurrence_binding_candidates::BoundReferencePopulation::ReferencePopulationRefused; +use crate::std_occurrence_binding_candidates::DeclarationExposureGrounding::ModuleLocalMemberExposure; +use crate::std_occurrence_binding_candidates::ReferenceBindingProjection::{ + ReferenceBindingProjectionAmbiguous, ReferenceBindingProjectionUnbound, +}; +pub use crate::std_occurrence_binding_candidates::{ + BoundReferencePopulation, CrossFileBindingClosureRow, CrossFileBindingProvenance, + DeclarationExposureGrounding, DirectFileDependency, ReferenceBindingProjection, +}; +pub use crate::std_occurrence_identity::{OccurrenceId, OccurrenceProjection, OccurrenceTransport}; +use crate::std_reference_derived_closure::ReferenceDerivedClosureFixedPoint::{ + ClosureFixedPointExhausted, ClosureFixedPointRefused, ClosureFixedPointSettled, +}; +use crate::std_reference_derived_closure::ReferenceDerivedClosureRefusal::{ + ClosureBindingRefused, ClosureNoEntryAdmitted, +}; +use crate::std_reference_derived_closure::ReferenceDerivedClosureStep::{ + ClosureStepAdvanced, ClosureStepRefused, ClosureStepSettled, +}; +pub use crate::std_reference_derived_closure::{ + reference_derived_closure_fixed_point, reference_derived_closure_initial, + reference_derived_closure_step, +}; +pub use crate::std_reference_derived_closure::{ + ParsedFileSupply, ReferenceDerivedClosureFixedPoint, ReferenceDerivedClosureRefusal, + ReferenceDerivedClosureState, ReferenceDerivedClosureStep, +}; +use crate::std_types::Bool::*; +pub use crate::std_types::{Bool, FilePath, List, NonEmptyStr}; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource; +use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource::{ + ParsedOccurrenceBindingSourceReady, ParsedOccurrenceBindingSourceRefused, +}; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::{ + occurrence_binding_inputs_from_transport, parse_authored_occurrence_binding_source, +}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +pub fn rdc_note() -> String { + thread_local! { + static CACHED: String = { + "THE LOADER FIXED POINT over the ordinary source-file universe.\n\nTWO POPULATIONS, NOT ONE. The candidate index is built over the WHOLE universe of parsed source files; the references BOUND are selected by the file they belong to, from the admitted closure only. That separation is what makes discovery possible: the declaration that a bare reference binds to is visible before the file carrying it is in the closure, and the edge derived from that binding is what admits the file. Collapsing the two deadlocks -- an unadmitted provider has no declaration in the index, so the reference is Unbound, so no provider file is derived, so nothing is ever admitted.\n\nA module-to-file index cannot substitute: storage realization says where a module lives and never which module declares a name.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_consumer_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/reference_derived_closure/consumer.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_provider_path() -> String { + thread_local! { + static CACHED: String = { + "src/v2/std/live_tree.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_second_provider_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/reference_derived_closure/second_provider.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_unrelated_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/reference_derived_closure/unrelated.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_consumer_module() -> String { + thread_local! { + static CACHED: String = { + "test.fixture.reference_derived_closure.consumer".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_later_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/reference_derived_closure/later.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_later_consumer_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/reference_derived_closure/later_consumer.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_early_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/reference_derived_closure/early.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_early_consumer_path() -> String { + thread_local! { + static CACHED: String = { + "dag/test/fixture/reference_derived_closure/early_consumer.dag".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn rdc_later_source() -> String { + (((((((((((("module test.fixture.reference_derived_closure.later\n".to_string() + + &"\n".to_string()) + + &"type EarlyInLater\n".to_string()) + + &" = EarlyOne\n".to_string()) + + &" | EarlyTwo\n".to_string()) + + &"\n".to_string()) + + &"fn uses(y: LaterInLater) -> LaterInLater {\n".to_string()) + + &" y\n".to_string()) + + &"}\n".to_string()) + + &"\n".to_string()) + + &"type LaterInLater\n".to_string()) + + &" = LaterOne\n".to_string()) + + &" | LaterTwo\n".to_string()) +} + +pub fn rdc_early_source() -> String { + (((((((("module test.fixture.reference_derived_closure.early\n".to_string() + + &"\n".to_string()) + + &"type EarlyOnly\n".to_string()) + + &" = OnlyOne\n".to_string()) + + &" | OnlyTwo\n".to_string()) + + &"\n".to_string()) + + &"fn uses(z: EarlyOnly) -> EarlyOnly {\n".to_string()) + + &" z\n".to_string()) + + &"}\n".to_string()) +} + +pub fn rdc_early_consumer_source() -> String { + (((("module test.fixture.reference_derived_closure.early_consumer\n".to_string() + + &"\n".to_string()) + + &"fn reads(e: early.EarlyOnly) -> early.EarlyOnly {\n".to_string()) + + &" e\n".to_string()) + + &"}\n".to_string()) +} + +pub fn rdc_later_consumer_source() -> String { + (((("module test.fixture.reference_derived_closure.later_consumer\n".to_string() + + &"\n".to_string()) + + &"fn reads(e: later.EarlyInLater) -> later.EarlyInLater {\n".to_string()) + + &" e\n".to_string()) + + &"}\n".to_string()) +} + +pub fn rdc_consumer_source() -> String { + (((("module test.fixture.reference_derived_closure.consumer\n".to_string() + + &"\n".to_string()) + + &"fn reads(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n" + .to_string()) + + &" d\n".to_string()) + + &"}\n".to_string()) +} + +pub fn rdc_provider_source() -> String { + (((("module v2.std.live_tree\n".to_string() + &"\n".to_string()) + + &"type LiveTreeDisposition\n".to_string()) + + &" = ReadsLiveTree\n".to_string()) + + &" | HermeticOnly\n".to_string()) +} + +pub fn rdc_second_provider_source() -> String { + (((("module test.fixture.reference_derived_closure.live_tree\n".to_string() + + &"\n".to_string()) + + &"type LiveTreeDisposition\n".to_string()) + + &" = SecondOne\n".to_string()) + + &" | SecondTwo\n".to_string()) +} + +pub fn rdc_unrelated_source() -> String { + ((("module test.fixture.reference_derived_closure.unrelated\n".to_string() + + &"\n".to_string()) + + &"type UnrelatedSubject\n".to_string()) + + &" = OnlyOne\n".to_string()) +} + +pub fn rdc_source_for(path: String) -> Option { + match (path.clone() == rdc_consumer_path()) { + true => Some(rdc_consumer_source()), + false => match (path.clone() == rdc_provider_path()) { + true => Some(rdc_provider_source()), + false => match (path.clone() == rdc_second_provider_path()) { + true => Some(rdc_second_provider_source()), + false => match (path.clone() == rdc_unrelated_path()) { + true => Some(rdc_unrelated_source()), + false => match (path.clone() == rdc_later_path()) { + true => Some(rdc_later_source()), + false => match (path.clone() == rdc_later_consumer_path()) { + true => Some(rdc_later_consumer_source()), + false => match (path.clone() == rdc_early_path()) { + true => Some(rdc_early_source()), + false => match (path.clone() == rdc_early_consumer_path()) { + true => Some(rdc_early_consumer_source()), + false => None, + }, + }, + }, + }, + }, + }, + }, + } +} + +pub fn rdc_grounding_for(path: String) -> DeclarationExposureGrounding { + DeclarationExposureGrounding::ModuleLocalMemberExposure +} + +pub fn rdc_supply_for(path: String) -> Option> { + match rdc_source_for(path.clone()) { + None => None, + Some(source) => { + match (*parse_authored_occurrence_binding_source(path.clone(), source.clone())).clone() + { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => None, + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport: t, + module_path, + .. + } => Some(Rc::new(ParsedFileSupply { + file: path.clone(), + row: cross_file_binding_closure_row( + t.clone(), + occurrence_binding_inputs_from_transport( + module_path.clone(), + t.clone(), + rdc_grounding_for(path.clone()), + ), + module_path.clone(), + path.clone(), + ), + })), + } + } + } +} + +pub fn rdc_no_supply() -> Rc>> { + Rc::new(vec![]) +} + +pub fn rdc_universe(paths: Rc>) -> Rc>> { + paths.clone().iter().cloned().fold( + rdc_no_supply(), + |acc: Rc>>, path: String| match rdc_supply_for(path.clone()) { + None => acc.clone(), + Some(row) => v1_rt::concat(acc.clone(), Rc::new(vec![row.clone()])), + }, + ) +} + +pub fn rdc_step(entry: String, universe: Rc>) -> Rc { + reference_derived_closure_step(reference_derived_closure_initial( + entry.clone(), + rdc_universe(universe.clone()), + )) +} + +pub fn rdc_fixed_point( + entry: String, + universe: Rc>, +) -> Rc { + reference_derived_closure_fixed_point(entry.clone(), rdc_universe(universe.clone())) +} + +pub fn rdc_ordinary_universe() -> Rc> { + Rc::new(vec![rdc_consumer_path(), rdc_provider_path()]) +} + +pub fn rdc_edges_name_provider(edges: Rc>>) -> bool { + edges + .clone() + .iter() + .cloned() + .fold(false, |found: bool, e: Rc| { + (found || (e.provider_file.clone() == rdc_provider_path())) + }) +} + +pub fn rdc_files_contain(files: Rc>, path: String) -> bool { + files + .clone() + .iter() + .cloned() + .fold(false, |found: bool, f: String| { + (found || (f.clone() == path.clone())) + }) +} + +pub fn witness_consumer_alone_discovers_the_provider_file_as_pending() -> bool { + match (*rdc_step(rdc_consumer_path(), rdc_ordinary_universe())).clone() { + ReferenceDerivedClosureStep::ClosureStepAdvanced { + state, + newly_pending, + edges, + provenance, + .. + } => { + ((((((((newly_pending.clone().len() as i64) == 1) + && rdc_files_contain(newly_pending.clone(), rdc_provider_path())) + && rdc_files_contain(state.admitted_files.clone(), rdc_consumer_path())) + && ((state.admitted_files.clone().len() as i64) == 2)) + && ((edges.clone().len() as i64) == 1)) + && rdc_edges_name_provider(edges.clone())) + && ((provenance.clone().len() as i64) >= 1)) + } + _ => false, + } +} + +pub fn witness_second_step_over_the_advanced_state_settles() -> bool { + match (*rdc_step(rdc_consumer_path(), rdc_ordinary_universe())).clone() { + ReferenceDerivedClosureStep::ClosureStepAdvanced { + state: advanced, .. + } => match (*reference_derived_closure_step(advanced.clone())).clone() { + ReferenceDerivedClosureStep::ClosureStepSettled { state, edges, .. } => { + ((((state.admitted_files.clone().len() as i64) == 2) + && ((edges.clone().len() as i64) == 1)) + && rdc_edges_name_provider(edges.clone())) + } + _ => false, + }, + _ => false, + } +} + +pub fn witness_fixed_point_settles_in_two_iterations_over_two_files() -> bool { + match (*rdc_fixed_point(rdc_consumer_path(), rdc_ordinary_universe())).clone() { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + admitted_files: admitted, + edges, + iterations, + .. + } => { + ((((((iterations.clone() == 2) && ((admitted.clone().len() as i64) == 2)) + && rdc_files_contain(admitted.clone(), rdc_consumer_path())) + && rdc_files_contain(admitted.clone(), rdc_provider_path())) + && ((edges.clone().len() as i64) == 1)) + && rdc_edges_name_provider(edges.clone())) + } + _ => false, + } +} + +pub fn witness_mentions_keep_receipts_while_edges_dedupe_without_a_self_edge() -> bool { + match (*rdc_fixed_point(rdc_consumer_path(), rdc_ordinary_universe())).clone() { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + edges, provenance, .. + } => { + (((((provenance.clone().len() as i64) == 3) && ((edges.clone().len() as i64) == 1)) + && edges.clone().iter().cloned().fold( + true, + |ok: bool, e: Rc| { + (ok && !(e.consumer_file.clone() == e.provider_file.clone())) + }, + )) + && provenance.clone().iter().cloned().fold( + false, + |found: bool, row: Rc| { + (found || (row.consumer_file.clone() == row.provider_file.clone())) + }, + )) + } + _ => false, + } +} + +pub fn witness_provider_outside_the_universe_refuses_as_unbound() -> bool { + match (*rdc_step(rdc_consumer_path(), Rc::new(vec![rdc_consumer_path()]))).clone() { + ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: refusal, .. + } => match (*refusal.clone()).clone() { + ReferenceDerivedClosureRefusal::ClosureBindingRefused { + population, + first_failure_locus: locus, + failure_count, + .. + } => match (*population.clone()).clone() { + BoundReferencePopulation::ReferencePopulationRefused { + first_failure: failure, + .. + } => { + ((((locus.authored_name.clone() + == "live_tree.LiveTreeDisposition".to_string()) + && (locus.diagnostic_span.clone().start.clone() > 0)) + && (failure_count.clone() >= 1)) + && match (*failure.clone()).clone() { + ReferenceBindingProjection::ReferenceBindingProjectionUnbound { + occurrence: _, + .. + } => true, + _ => false, + }) + } + _ => false, + }, + _ => false, + }, + _ => false, + } +} + +pub fn witness_entry_absent_from_the_universe_refuses() -> bool { + match (*rdc_step(rdc_consumer_path(), Rc::new(vec![rdc_provider_path()]))).clone() { + ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: refusal, .. + } => match (*refusal.clone()).clone() { + ReferenceDerivedClosureRefusal::ClosureNoEntryAdmitted => true, + _ => false, + }, + _ => false, + } +} + +pub fn witness_two_declaring_files_refuse_as_ambiguous_naming_both() -> bool { + match (*rdc_step( + rdc_consumer_path(), + Rc::new(vec![ + rdc_consumer_path(), + rdc_provider_path(), + rdc_second_provider_path(), + ]), + )) + .clone() + { + ReferenceDerivedClosureStep::ClosureStepRefused { + refusal: refusal, .. + } => match (*refusal.clone()).clone() { + ReferenceDerivedClosureRefusal::ClosureBindingRefused { + population, + first_failure_locus: locus, + failure_count, + .. + } => match (*population.clone()).clone() { + BoundReferencePopulation::ReferencePopulationRefused { + first_failure: failure, + .. + } => match (*failure.clone()).clone() { + ReferenceBindingProjection::ReferenceBindingProjectionAmbiguous { + candidates, + .. + } => { + (((locus.authored_name.clone() + == "live_tree.LiveTreeDisposition".to_string()) + && (failure_count.clone() >= 1)) + && !(candidates + .first + .clone() + .containment + .clone() + .terminal + .clone() + .value + .clone() + == candidates + .second + .clone() + .containment + .clone() + .terminal + .clone() + .value + .clone())) + } + _ => false, + }, + _ => false, + }, + _ => false, + }, + _ => false, + } +} + +pub fn witness_unreferenced_universe_file_is_not_admitted() -> bool { + match (*rdc_fixed_point( + rdc_consumer_path(), + Rc::new(vec![ + rdc_consumer_path(), + rdc_provider_path(), + rdc_unrelated_path(), + ]), + )) + .clone() + { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + admitted_files: admitted, + edges, + .. + } => { + ((((admitted.clone().len() as i64) == 2) + && !rdc_files_contain(admitted.clone(), rdc_unrelated_path())) + && ((edges.clone().len() as i64) == 1)) + } + _ => false, + } +} + +pub fn rdc_binding_refusal_locus( + outcome: Rc, +) -> Option> { + match (*outcome.clone()).clone() { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointRefused { refusal, .. } => { + match (*refusal.clone()).clone() { + ReferenceDerivedClosureRefusal::ClosureBindingRefused { + first_failure_locus: locus, + .. + } => Some(locus.clone()), + _ => None, + } + } + _ => None, + } +} + +pub fn witness_later_declaration_refuses_identically_under_both_entries() -> bool { + { + let universe = Rc::new(vec![rdc_later_consumer_path(), rdc_later_path()]); + match rdc_binding_refusal_locus(rdc_fixed_point(rdc_later_path(), universe.clone())) { + None => false, + Some(as_entry) => match rdc_binding_refusal_locus(rdc_fixed_point( + rdc_later_consumer_path(), + universe.clone(), + )) { + None => false, + Some(as_provider) => { + ((((as_entry.authored_name.clone() == "LaterInLater".to_string()) + && (as_provider.authored_name.clone() == as_entry.authored_name.clone())) + && (as_provider.diagnostic_span.clone().start.clone() + == as_entry.diagnostic_span.clone().start.clone())) + && (as_provider.diagnostic_span.clone().end.clone() + == as_entry.diagnostic_span.clone().end.clone())) + } + }, + } + } +} + +pub fn witness_earlier_declaration_binds_under_both_entries() -> bool { + { + let universe = Rc::new(vec![rdc_early_consumer_path(), rdc_early_path()]); + match (*rdc_fixed_point(rdc_early_path(), universe.clone())).clone() { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + admitted_files: alone, + .. + } => match (*rdc_fixed_point(rdc_early_consumer_path(), universe.clone())).clone() { + ReferenceDerivedClosureFixedPoint::ClosureFixedPointSettled { + admitted_files: reached, + .. + } => { + (((rdc_files_contain(alone.clone(), rdc_early_path()) + && !rdc_files_contain(alone.clone(), rdc_early_consumer_path())) + && rdc_files_contain(reached.clone(), rdc_early_path())) + && rdc_files_contain(reached.clone(), rdc_early_consumer_path())) + } + _ => false, + }, + _ => false, + } + } +} diff --git a/src/v1/tests/claim/import_deletion_bridge_witness_test.dag b/src/v1/tests/claim/import_deletion_bridge_witness_test.dag new file mode 100644 index 00000000000..9589b9dfead --- /dev/null +++ b/src/v1/tests/claim/import_deletion_bridge_witness_test.dag @@ -0,0 +1,1258 @@ +module v1.tests.claim.import_deletion_bridge_witness + +import std.types { Bool, String, List, Int, NonEmptyStr, FilePath } +import std.occurrence_identity { + OccurrenceId, OccurrenceIndexEntry, OccurrenceTransport, + ReferenceOccurrence, DeclarationOccurrence, + OccurrenceCategory, TypeOccurrence, CallableOccurrence, FieldOccurrence, + MethodOccurrence, LexicalValueOccurrence, ConstructorOccurrence, + NamespaceSegmentOccurrence, +} +import std.occurrence_binding_candidates { + OccurrenceBindingCandidateInputs, + CrossFileBindingClosureRow, + AssembledCrossFileBindingClosure, + AssembledCrossFileBindingClosureReady, + AssembledCrossFileBindingClosureRefused, + AssembledCrossFileBindingClosureRefusal, + AssembledCrossFileBindingClosureModulePathFileRefused, + assemble_cross_file_binding_closure, + BoundReferenceProvider, + BoundReferencePopulation, + AllReferencesBound, + ReferencePopulationRefused, + ReferenceBindingProjection, + ReferenceBindingProjectionUnbound, + ReferenceBindingProjectionAmbiguous, + StructuralBindingWalk, + StructuralBindingWalkReady, + StructuralBindingWalkRefused, + StructuralBindingIndexRefusal, + structural_binding_walk_selected_references, + ReferenceDerivedDependencyProjection, + ReferenceDerivedDependencyProjectionReady, + ReferenceDerivedDependencyProjectionBindingRefused, + ReferenceDerivedDependencyProjectionFileRefused, + reference_derived_dependency_projection, + ModulePathFileIndexRefusal, + ModulePathFileIndexRefused, + ModulePathFileConflict, + CrossFileBindingProvenance, + CrossFileBindingProvenancePopulation, + AllCrossFileBindingsBound, + CrossFileBindingPopulationBindingRefused, + CrossFileBindingPopulationRefused, + cross_file_binding_provenance_from_bound_population, + direct_file_dependencies_from_provenances, + DirectFileDependency, + ModulePathFileRow, + ModulePathFileIndexReady, + module_path_file_index_from_rows, + DeclarationExposureGrounding, + ModuleLocalMemberExposure, + ModuleLocalMemberExposure, + ReferenceBindingProjectionBound, + resolve_reference_binding_via_structural_candidates, + ReferenceDependencyAdmission, + ReferenceDependencyAdmittedClauseE, + ReferenceDependencyAdmittedTypeReference, + ReferenceDependencyExcluded, + reference_dependency_admission_for_category, + joined_dependency_binding_walk, + occurrence_category_clause_e_dependency_inducing_verdict, + OccurrenceCategoryClauseEDependencyInducing, + OccurrenceCategoryClauseEDependencyNotInducing, + occurrence_candidate_index_build, + OccurrenceCandidateIndexBuild, + OccurrenceCandidateIndexReady, + resolve_reference_path, + ReferencePathResolution, + ReferencePathResolved, + ReferencePathHeadUnbound, + ReferencePathHeadAmbiguous, + ReferencePathSegmentUnbound, + ReferencePathSegmentAmbiguous, + ReferencePathBuildRefused, + ReferencePathNotANamespaceProjection, +} +import std.occurrence_binding { + AmbiguousBindingCandidates, + BindingCandidate, +} +import v1.gunbc.occurrence_binding_parser_walk { + ParsedOccurrenceBindingSource, + ParsedOccurrenceBindingSourceReady, + ParsedOccurrenceBindingSourceRefused, + parse_authored_occurrence_binding_source, + occurrence_binding_inputs_from_transport, +} + +data stage0_emission_model_gap_note: String = "BLOCKER FOUND WHILE MAKING THIS BRANCH MECHANICALLY COMPLETE — an emission-model gap, not a registration oversight, and it is the reason regen refuses with this witness present. + +Reading real source requires filesystem_read, which requires the Filesystem SERVICE in the import closure. Any service in the v1 emission closure makes the emitter generate `dry_run.rs` — service dry-run support (`pub struct DryRunMode(pub bool)`), verified by emitting fresh and reading the file's own header rather than inferred from the name. + +`dry_run.rs` HAS NO SOURCE MODULE. Every other emitted stage0 file corresponds to one (`v1_rt.rs` is module v1.rt), and `gunbc.stage0_emit_plan` derives the plan FROM the parse-observed module closure — so a file emitted as a consequence of service PRESENCE, rather than from a module, is structurally invisible to the derivation. regen therefore refuses it as unregistered, and correctly refuses the obvious repair too: the CI auto-heal hand-added two rows to the projection, which is exactly what the refusal text forbids. + +No src/v1 module used a service before, which is why this never surfaced. It is not caused by this lane and is not this lane's to fix casually: the plan model must learn that service presence in the closure contributes emitter-support files, or those files must be classified through v2.compiler.self_host.frontier. Either is a change to a load-bearing regen gate and needs its own modelling, not a path added here. + +CONSEQUENCE FOR THIS WITNESS, stated rather than worked around: it cannot both live under src/v1 (needed to import v1.gunbc.occurrence_binding_parser_walk, which the dag+src/v2 compile-clean roots cannot see) and read the live tree (which drags a service into the v1 seed). That is a genuine placement contradiction, not a filing preference, and it is the next thing to resolve." + +data import_deletion_bridge_offline_recipe: String = "OFFLINE LOCAL RECIPE (requires the v1 parser pool, exactly as the sibling occurrence_binding_parser_walk witness does). This file lives under src/v1/tests/claim/ and NOT under dag/test/claim/ for a structural reason, not a filing preference: it imports v1.gunbc.occurrence_binding_parser_walk, and the compile-clean gate compiles dag + src/v2 only, so a copy under dag/ fails to resolve the module it exists to exercise. Placing it here is the same resolution the sibling witness already made. + + target/release/claim_batch --source-root dag --source-root src/v1 --source-root src/v2 --entry src/v1/tests/claim/import_deletion_bridge_witness_test.dag --claim-run --wet + +THE FUNCTION LIST IS DELIBERATELY NOT ENUMERATED. This file is enrolled file-grain, so the empty-function authority expands it, and a hand-copied roster of test fn names beside a file that already names them is a second representation that rots on the next rename. An earlier revision carried such a list, and it named three functions -- two of which no longer exist and one of which was described as an expected red. NOTHING HERE IS EXPECTED RED ANY MORE: the positive keystone greened when role-directed minting landed, and the provider is SUPPLIED by this file rather than discovered from the tree, which is stated on the keystone itself so the green is not read as provider discovery. + +STATED PLAINLY BECAUSE IT IS A REAL COVERAGE GAP, NOT A DETAIL: witnesses here are not per-PR discovery-enrolled, so these controls execute locally and on demand rather than on every change. DISSOLVE-ON is the sibling's, unchanged and shared rather than re-minted: witness_layer_roots admits src/v1 for parser-transport witnesses without Node ambiguity, or the execution corpus carries per-entry source roots -- then this recipe deletes and discovery enrollment suffices." + +data bridge_witness_note: String = "CHECKPOINT 1 OF IMPORT DELETION — the bridge from the real source population to production occurrence binding, proven on the largest falsifier rather than on a fixture pair. + +WHAT THIS PROVES, AND WHAT IT DOES NOT. It is a SUPPLIED-PROVIDER binding keystone: the provider path is an INPUT to the operation under test, so it establishes that ordinary parser facts can bind across files when handed the right provider. It does NOT establish provider DISCOVERY, and it does not yet project a provider-file edge — it checks provider module identity only. Those are separate milestones and this witness must not be read as closing them. + +WHAT THIS PROVES. An import-less consumer reaches a declaration in a REAL production module — v2.std.live_tree, read off disk, not a fixture provider and not an embedded source string — with no import participating in the decision anywhere. LiveTreeDisposition is the subject because it is the largest population that loses its sole provider under strip: 1,673 references corpus-wide. If reference-derived binding cannot reach this declaration, deleting imports cannot work at all. + +SCOPE REDUCED, LOUDLY, WITH A TRIGGER. An earlier revision read BOTH files off disk, so the provider was the ordinary production module rather than anything authored for this test — strictly better evidence. It is not possible today: filesystem_read needs the Filesystem service, and a service in the v1 emission closure emits an unmodelled `dry_run.rs` (see the gap note above), while the witness must live under src/v1 to import the module it exercises at all. Both sources are therefore EMBEDDED, exactly as the sibling parser-walk witness does. + +WHAT THAT COSTS, so nobody reads more into this than it carries: the provider is now an authored copy of v2.std.live_tree's type declaration, not the real module, so this no longer witnesses anything about the live corpus and cannot catch drift between the two. It proves the binding mechanism only. DISSOLVE-ON: the stage0 emission model accounts for service-support files (or classifies them through v2.compiler.self_host.frontier), after which both sides return to filesystem_read and the provider becomes the real module again. + +THE PARSER FINDING BELOW DOES NOT DEPEND ON THIS. It was established by a structural census of v1.02_parse's declaration-minting sites, not by reading the corpus, so the reduction costs the keystone's reach and costs the finding nothing. + +WHY PER-REFERENCE RESOLVE AND NOT structural_binding_walk. The walk demands AllReferencesBound over the whole transport. A real production module references types this two-file closure does not contain, so the walk would refuse for reasons that have nothing to do with the claim — and a refusal read as a failure here would be a false red. Resolving the one reference under test keeps the discriminator on the claim. + +THE DISCRIMINATING CONTROL IS THE SECOND TEST, NOT AN ASSERTION IN THIS ONE. Binding the reference proves little on its own: it must FAIL when the provider is absent from the assembled closure. Without that control this witness would pass on any closure that happened to contain the name, which is precisely the pool-membership coincidence the whole lane exists to eliminate." + +data idb_live_tree_source_path: String = "src/v2/std/live_tree.dag" +data idb_consumer_source_path: String = "dag/test/fixture/import_deletion_bridge/live_tree_consumer_bare.dag" +data idb_provider_module_name: String = "v2.std.live_tree" +data idb_subject_name: String = "live_tree.LiveTreeDisposition" +data idb_subject_leaf_name: String = "LiveTreeDisposition" + +data idb_missing_provider_name: String = "missing.MissingProviderType" + +fn idb_consumer_source() -> String { + "module test.fixture.import_deletion_bridge.live_tree_consumer_bare\n\nfn bridge_consumer_reads_disposition(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n d\n}\n" +} + +fn idb_provider_source() -> String { + "module v2.std.live_tree\n\ntype LiveTreeDisposition\n = ReadsLiveTree\n | SubstrateInputsOnly\n" +} + + + + +data idb_totality_note: String = "THE TOTALITY LAW, and the defect that made it necessary. + +An earlier revision of this witness reached its 2-provenance/1-edge result through a FAIL-OPEN ADAPTER: it matched only ReferenceBindingProjectionBound, sent every other outcome to a discard arm, and then hand-constructed AllReferencesBound from the surviving subset. That is the absorbing fallback DESIGN section 5 forbids, built into the evidence for a lane whose whole purpose is deleting a fail-open. It would have reported a complete closure for a population where one reference bound and another did not -- which is the false-skip direction, the one that silently under-loads. + +WHAT REPLACED IT. One assembly, then the production total path and nothing local: structural_binding_walk_selected_references returns a BoundReferencePopulation that is either AllReferencesBound or ReferencePopulationRefused carrying the first failure, and reference_derived_dependency_projection turns that into Ready or a typed binding/file refusal. This file never constructs AllReferencesBound, never folds projections itself, and never uses an empty list to mean a stage refused. + +WHY THE OUTCOME IS A COPRODUCT. Every stage that can refuse is carried as its own arm, reusing the production refusal types (StructuralBindingIndexRefusal, ModulePathFileIndexRefusal, ReferenceDerivedDependencyProjection's own refusal arms) rather than minting a parallel vocabulary. The two arms that are genuinely local are about the FIXTURE's own population -- an unknown candidate path and a reference-count mismatch -- because those are properties of the controlled input, not of the substrate under test. + +WHY THE SELECTION REFUSES ON A COUNT MISMATCH. The subject is mentioned exactly twice by construction. Operating on whichever references happen to be found would make the multiplicity claim self-fulfilling: a parser that stamped one reference would produce one row and the row count would agree with itself. Refusing unless exactly two are observed is what keeps the 2-to-1 law a claim about the projection rather than a restatement of the input. + +WHY NOT THE TOP-LEVEL HELPER. reference_derived_dependency_projection_for_assembled_closure selects its references through the clause-e dependency-inducing filter, which deliberately EXCLUDES TypeOccurrence -- that is the N2 lane. The subject here is a type reference, so that helper would select zero references, report Ready with zero provenance rows and zero edges, and every count assertion would pass vacuously. Passing the selected references to the walk directly is what avoids buying a green with an empty population; moving TypeOccurrence into clause e to reuse the walk is the wrong repair and belongs to nobody." + +type IdbCandidate { + file: String + source: String +} + +data idb_candidate_population_note: String = "THE CANDIDATE POPULATION, explicit rather than defaulted. An earlier revision resolved a path to source with `if path == consumer then consumer_source else provider_source`, so ANY path -- including one deliberately unrelated -- silently received the real provider's text. Under that shape an unrelated-candidate control cannot exist, because unrelated and actual provider are not representable as different contents, and a provider-discovery control would be actively misleading. A path absent from this population now REFUSES; it never defaults to provider contents." + +fn idb_candidates() -> List { + [ IdbCandidate { file: idb_consumer_source_path, source: idb_consumer_source() }, + IdbCandidate { file: idb_live_tree_source_path, source: idb_provider_source() }, + IdbCandidate { file: idb_unrelated_source_path, source: idb_unrelated_source() }, + IdbCandidate { file: idb_second_provider_source_path, source: idb_second_provider_source() }, + IdbCandidate { file: idb_malformed_source_path, source: idb_malformed_source() }, + IdbCandidate { file: idb_partial_consumer_source_path, source: idb_partial_consumer_source() }, + IdbCandidate { file: idb_same_file_source_path, source: idb_same_file_source() }, + IdbCandidate { file: idb_conflicting_source_path, source: idb_conflicting_source() }, + IdbCandidate { file: idb_type_consumer_source_path(), source: idb_type_consumer_source() }, + IdbCandidate { file: idb_path_consumer_source_path, source: idb_path_consumer_source() }, + IdbCandidate { file: idb_duplicate_member_source_path, source: idb_duplicate_member_source() }, + ] +} + +data idb_path_consumer_source_path: String = "dag/test/fixture/import_deletion_bridge/path_controls_consumer.dag" + +data idb_path_consumer_source_note: String = "A DEDICATED consumer for the reference-path controls, deliberately not the type-join consumer those other witnesses count references in. Three references in one file: one that must resolve to a terminal declaration, one whose head resolves and whose segment names nothing, and one whose head names nothing at all. They share a file because the controls must be read against the SAME parse -- a segment refusal is only evidence that head and segment are distinguishable if a resolving path exists beside it in the same population." + +fn idb_path_consumer_source() -> String { + "module test.fixture.import_deletion_bridge.path_controls_consumer\n\nfn reads_good(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n d\n}\n\nfn reads_missing_member(d: live_tree.NoSuchMember) -> live_tree.NoSuchMember {\n d\n}\n\nfn reads_missing_head(d: no_such_module.Anything) -> no_such_module.Anything {\n d\n}\n\nfn reads_ordinary_field(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n d.ordinary_field\n}\n\nfn reads_duplicated(t: duplicated.Twice) -> duplicated.Twice {\n t\n}\n\nfn reads_three_segments(d: live_tree.LiveTreeDisposition.ReadsLiveTree) -> live_tree.LiveTreeDisposition.ReadsLiveTree {\n d\n}\n\nfn reads_fully_qualified(d: v2.std.live_tree.LiveTreeDisposition) -> v2.std.live_tree.LiveTreeDisposition {\n d\n}\n" +} + +data idb_unrelated_source_path: String = "dag/test/fixture/import_deletion_bridge/unrelated_module.dag" + +fn idb_unrelated_source() -> String { + "module test.fixture.import_deletion_bridge.unrelated_module\n\ntype UnrelatedCarrier\n = UnrelatedOne\n | UnrelatedTwo\n" +} + +data idb_second_provider_source_path: String = "dag/test/fixture/import_deletion_bridge/second_live_tree.dag" + +fn idb_second_provider_source() -> String { + "module test.fixture.import_deletion_bridge.live_tree\n\ntype LiveTreeDisposition\n = ReadsLiveTree\n | SubstrateInputsOnly\n" +} + +data idb_conflicting_source_path: String = "dag/test/fixture/import_deletion_bridge/live_tree_second_home.dag" + +fn idb_conflicting_source() -> String { + "module v2.std.live_tree\n\ntype LiveTreeDispositionElsewhere\n = ElsewhereOne\n | ElsewhereTwo\n" +} + +data idb_malformed_source_path: String = "dag/test/fixture/import_deletion_bridge/malformed_provider.dag" + +fn idb_malformed_source() -> String { + "module test.fixture.import_deletion_bridge.malformed_provider\n\ntype = = =\n" +} + +data idb_partial_consumer_source_path: String = "dag/test/fixture/import_deletion_bridge/partial_consumer.dag" + +fn idb_partial_consumer_source() -> String { + "module test.fixture.import_deletion_bridge.partial_consumer\n\nfn bridge_partial(a: live_tree.LiveTreeDisposition, b: missing.MissingProviderType) -> live_tree.LiveTreeDisposition {\n a\n}\n" +} + +data idb_same_file_source_path: String = "dag/test/fixture/import_deletion_bridge/same_file.dag" + +fn idb_same_file_source() -> String { + "module test.fixture.import_deletion_bridge.same_file\n\ntype LiveTreeDisposition\n = ReadsLiveTree\n | SubstrateInputsOnly\n\nfn same_file_reads(d: LiveTreeDisposition) -> LiveTreeDisposition {\n d\n}\n" +} + +fn idb_source_for_candidate(path: String) -> String? { + fold(idb_candidates(), init: none, f: (found, candidate) => + match found { + Present { value: _ } => found + Absent => + match candidate.file == path { + true => Present { value: candidate.source } + false => Absent + } + }) +} + +type IdbBridge + = IdbBridgeProjected { projection: ReferenceDerivedDependencyProjection } + | IdbBridgeWalkRefused { refusal: StructuralBindingIndexRefusal } + | IdbBridgeIndexRefused { refusal: ModulePathFileIndexRefusal } + | IdbBridgeAssemblyRefused { refusal: AssembledCrossFileBindingClosureRefusal } + | IdbBridgeParseRefused { file: String } + | IdbBridgeCandidateUnknown { file: String } + | IdbBridgeSelectionRefused { observed: Int } + +fn idb_parsed_row(path: String) -> ParsedOccurrenceBindingSource? { + match idb_source_for_candidate(path: path) { + Absent => none + Present { value: source } => + Present { value: parse_authored_occurrence_binding_source(file: path, source: source) } + } +} + +fn idb_no_closure_rows() -> List { [] } + +fn idb_closure_row_for( + path: String, + grounding: DeclarationExposureGrounding +) -> List { + match idb_parsed_row(path: path) { + Absent => idb_no_closure_rows() + Present { value: ParsedOccurrenceBindingSourceRefused } => idb_no_closure_rows() + Present { value: ParsedOccurrenceBindingSourceReady { transport: t, module_path: m } } => + [ CrossFileBindingClosureRow { + transport: t, + inputs: occurrence_binding_inputs_from_transport( + module_path: m, transport: t, grounding: grounding, + ), + module_path: m, + file_path: path, + } ] + } +} + +fn idb_index_entry_for_occurrence( + transport: OccurrenceTransport, + occurrence: OccurrenceId +) -> OccurrenceIndexEntry? { + fold(transport.index.entries, init: none, f: (found, entry) => + match found { + Present { value: _ } => found + Absent => + match entry.projection.occurrence.value == occurrence.value { + true => Present { value: entry } + false => Absent + } + }) +} + +fn idb_no_references() -> List { [] } + +fn idb_name_in(names: List, name: String) -> Bool { + fold(names, init: false, f: (found, candidate) => found || (candidate == name)) +} + +fn idb_references_in_names( + transport: OccurrenceTransport, + names: List +) -> List { + fold(transport.references, init: idb_no_references(), f: (acc, reference) => + match idb_index_entry_for_occurrence(transport: transport, occurrence: reference.occurrence) { + Absent => acc + Present { value: entry } => + match idb_name_in(names: names, name: entry.projection.authored_name) { + true => concat(acc, [reference]) + false => acc + } + }) +} + +fn idb_references_named(transport: OccurrenceTransport, name: String) -> List { + fold(transport.references, init: idb_no_references(), f: (acc, reference) => + match idb_index_entry_for_occurrence(transport: transport, occurrence: reference.occurrence) { + Absent => acc + Present { value: entry } => + match entry.projection.authored_name == name { + true => concat(acc, [reference]) + false => acc + } + }) +} + +fn idb_references_named_in_consumer(consumer_path: String) -> List { + match idb_parsed_row(path: consumer_path) { + Absent => idb_no_references() + Present { value: ParsedOccurrenceBindingSourceRefused } => idb_no_references() + Present { value: ParsedOccurrenceBindingSourceReady { transport: t, module_path: _ } } => + idb_references_named(transport: t, name: idb_subject_name) + } +} + +fn idb_project_from_assembled( + closure: AssembledCrossFileBindingClosure, + subject_names: List, + expected_references: Int +) -> IdbBridge { + match closure { + AssembledCrossFileBindingClosureRefused { refusal: refusal } => + IdbBridgeAssemblyRefused { refusal: refusal } + AssembledCrossFileBindingClosureReady { + transport: t, inputs: i, module_files: rows, consumer_file: _, + } => + let selected = idb_references_in_names(transport: t, names: subject_names) + match length(selected) == expected_references { + false => IdbBridgeSelectionRefused { observed: length(selected) } + true => + match module_path_file_index_from_rows(rows: rows) { + ModulePathFileIndexRefused { refusal: refusal } => + IdbBridgeIndexRefused { refusal: refusal } + ModulePathFileIndexReady { entries: entries } => + match structural_binding_walk_selected_references( + transport: t, inputs: i, references: selected, + ) { + StructuralBindingWalkRefused { refusal: refusal } => + IdbBridgeWalkRefused { refusal: refusal } + StructuralBindingWalkReady { population: population } => + IdbBridgeProjected { + projection: reference_derived_dependency_projection( + population: population, module_files: entries, + ), + } + } + } + } + } +} + +fn idb_bridge_for( + consumer_path: String, + provider_paths: List, + subject_names: List, + expected_references: Int +) -> IdbBridge { + match idb_source_for_candidate(path: consumer_path) { + Absent => IdbBridgeCandidateUnknown { file: consumer_path } + Present { value: _ } => + match fold(provider_paths, init: none, f: (missing, p) => + match missing { + Present { value: _ } => missing + Absent => + match idb_source_for_candidate(path: p) { + Absent => Present { value: p } + Present { value: _ } => Absent + } + }) { + Present { value: unknown } => IdbBridgeCandidateUnknown { file: unknown } + Absent => + match first(idb_closure_row_for(path: consumer_path, grounding: ModuleLocalMemberExposure)) { + Absent => IdbBridgeParseRefused { file: consumer_path } + Present { value: consumer_row } => + let provider_rows = fold(provider_paths, init: idb_no_closure_rows(), f: (acc, p) => + concat(acc, idb_closure_row_for(path: p, grounding: ModuleLocalMemberExposure))) + match length(provider_rows) == length(provider_paths) { + false => IdbBridgeParseRefused { file: idb_malformed_source_path } + true => + idb_project_from_assembled( + closure: assemble_cross_file_binding_closure( + consumer: consumer_row, providers: provider_rows, + ), + subject_names: subject_names, + expected_references: expected_references, + ) + } + } + } + } +} + +fn idb_supplied_bridge() -> IdbBridge { + idb_bridge_for( + consumer_path: idb_consumer_source_path, + provider_paths: [idb_live_tree_source_path], + subject_names: [idb_subject_name], + expected_references: 2, + ) +} + +fn idb_ready_projection(bridge: IdbBridge) -> ReferenceDerivedDependencyProjection? { + match bridge { + IdbBridgeProjected { projection: projection } => Present { value: projection } + _ => none + } +} + +fn idb_provenances(bridge: IdbBridge) -> List { + match bridge { + IdbBridgeProjected { + projection: ReferenceDerivedDependencyProjectionReady { + binding_provenance: rows, file_dependencies: _, bound_population: _, + }, + } => rows + _ => [] + } +} + +fn idb_file_dependencies(bridge: IdbBridge) -> List { + match bridge { + IdbBridgeProjected { + projection: ReferenceDerivedDependencyProjectionReady { + binding_provenance: _, file_dependencies: edges, bound_population: _, + }, + } => edges + _ => [] + } +} + +fn idb_is_ready(bridge: IdbBridge) -> Bool { + match bridge { + IdbBridgeProjected { + projection: ReferenceDerivedDependencyProjectionReady { + binding_provenance: _, file_dependencies: _, bound_population: _, + }, + } => true + _ => false + } +} + +fn idb_population_refused_unbound(bridge: IdbBridge) -> Bool { + match bridge { + IdbBridgeProjected { + projection: ReferenceDerivedDependencyProjectionBindingRefused { population: population }, + } => + match population { + ReferencePopulationRefused { first_failure: failure, more_failures: _ } => + match failure { + ReferenceBindingProjectionUnbound { occurrence: _ } => true + _ => false + } + _ => false + } + _ => false + } +} + +test fn witness_import_less_reference_reaches_the_supplied_provider() -> Bool { + match idb_supplied_bridge() { + IdbBridgeProjected { + projection: ReferenceDerivedDependencyProjectionReady { + binding_provenance: rows, file_dependencies: _, bound_population: _, + }, + } => + fold(rows, init: length(rows) == 2, f: (ok, row) => + ok && (row.provider_file as String) == idb_live_tree_source_path) + _ => false + } +} + +test fn witness_consumer_carries_no_import_declaration() -> Bool { + let source = idb_consumer_source() + !string_contains(s: source, pattern: "import ") + && string_contains(s: source, pattern: idb_subject_name) +} + +test fn witness_consumer_mentions_the_subject_twice() -> Bool { + length(idb_references_named_in_consumer(consumer_path: idb_consumer_source_path)) == 2 +} + +test fn witness_two_mentions_project_two_binding_provenance_rows() -> Bool { + length(idb_provenances(bridge: idb_supplied_bridge())) == 2 +} + +test fn witness_provenance_rows_dedupe_to_one_provider_file_dependency() -> Bool { + let edges = idb_file_dependencies(bridge: idb_supplied_bridge()) + match length(edges) == 1 { + false => false + true => + match first(edges) { + Absent => false + Present { value: edge } => + (edge.provider_file as String) == idb_live_tree_source_path + && (edge.consumer_file as String) == idb_consumer_source_path + && !((edge.consumer_file as String) == (edge.provider_file as String)) + } + } +} + +data idb_withheld_control_note: String = "THE WITHHELD-PROVIDER CONTROL, asserting a typed refusal rather than an empty result. The earlier version asserted zero provenance rows and zero edges, which is satisfied equally by a REFUSAL and by a successful projection that produced nothing -- and only the first is safe for a loader. This asserts the population is ReferencePopulationRefused with an Unbound first failure, and separately that the projection is NOT Ready, so an empty success cannot pass." + +test fn witness_withheld_provider_refuses_as_unbound() -> Bool { + let bridge = idb_bridge_for( + consumer_path: idb_consumer_source_path, provider_paths: [], + subject_names: [idb_subject_name], expected_references: 2, + ) + idb_population_refused_unbound(bridge: bridge) && !idb_is_ready(bridge: bridge) +} + +data idb_fail_open_regression_receipt: String = "MEASURED, BY REINSTATING THE DEFECT. The fail-open adapter this file used to contain was put back temporarily -- filter to Bound, hand-mint AllReferencesBound from the survivors -- and exactly TWO rows red: witness_withheld_provider_refuses_as_unbound and witness_partially_bound_population_refuses_whole. Every other row, INCLUDING the 2-provenance-to-1-edge pair, passed under the defect. That is the whole argument for these two controls: the positive law never detected the fail-open and never could, because a fabricated success is still a success. They are the executing evidence that the totality property holds, so they stay enrolled rather than retiring now that the adapter is gone (DESIGN section 4b(4))." + +data idb_partial_population_note: String = "THE PLANTED PARTIAL POPULATION -- the control that catches the fail-open this file used to contain. The consumer requires TWO cross-module type references and only ONE provider is supplied, so one reference binds and the other cannot. The whole population must refuse. Under the discarded adapter this case emitted a provider-file edge from the single successful binding and read as a complete closure." + +test fn witness_partially_bound_population_refuses_whole() -> Bool { + let bridge = idb_bridge_for( + consumer_path: idb_partial_consumer_source_path, + provider_paths: [idb_live_tree_source_path], + subject_names: [idb_subject_name, idb_missing_provider_name], + expected_references: 3, + ) + !idb_is_ready(bridge: bridge) + && length(idb_provenances(bridge: bridge)) == 0 + && length(idb_file_dependencies(bridge: bridge)) == 0 +} + +data idb_pool_equality_note: String = "POOL INDEPENDENCE, asserted as an EQUALITY rather than as a still-passes. Adding an unrelated module to the candidate population must leave the binding identities and the edge set unchanged -- that is what distinguishes derivation from pool-membership coincidence, which is the failure mode this whole lane exists to eliminate. Asserting only that the positive still greens would not detect an edge set that grew." + +fn idb_edge_key(edges: List) -> String { + fold(edges, init: "", f: (acc, edge) => + acc + "|" + (edge.consumer_file as String) + ">" + (edge.provider_file as String)) +} + +fn idb_provenance_key(rows: List) -> String { + fold(rows, init: "", f: (acc, row) => + acc + "|" + to_string(row.reference_occurrence.value) + ">" + to_string(row.declaration_occurrence.value)) +} + +test fn witness_unrelated_candidate_changes_nothing() -> Bool { + let without = idb_supplied_bridge() + let with_unrelated = idb_bridge_for( + consumer_path: idb_consumer_source_path, + provider_paths: [idb_live_tree_source_path, idb_unrelated_source_path], + subject_names: [idb_subject_name], + expected_references: 2, + ) + idb_is_ready(bridge: without) + && idb_is_ready(bridge: with_unrelated) + && (idb_edge_key(edges: idb_file_dependencies(bridge: without)) == + idb_edge_key(edges: idb_file_dependencies(bridge: with_unrelated))) + && (idb_provenance_key(rows: idb_provenances(bridge: without)) == + idb_provenance_key(rows: idb_provenances(bridge: with_unrelated))) +} + +data idb_same_file_note: String = "SAME-FILE RULING, exercised rather than trusted. A consumer whose provider declaration lives in its OWN file must retain its binding provenance and emit NO file self-edge -- the carrier law says so, and a projection that emitted a self-edge would make every file depend on itself and defeat the closure." + +test fn witness_same_file_binding_retains_provenance_without_self_edge() -> Bool { + let bridge = idb_bridge_for( + consumer_path: idb_same_file_source_path, provider_paths: [], + subject_names: [idb_subject_leaf_name], expected_references: 2, + ) + idb_is_ready(bridge: bridge) + && length(idb_provenances(bridge: bridge)) == 2 + && length(idb_file_dependencies(bridge: bridge)) == 0 +} + +data idb_unknown_candidate_note: String = "A path absent from the candidate population REFUSES and is never resolved to provider contents by default. This is the control for the defaulting behaviour the candidate rows replaced: under the old path switch this call would have parsed the real provider's text under an unrelated storage path and bound successfully, which would make every later provider-discovery control meaningless." + +test fn witness_unknown_candidate_path_refuses() -> Bool { + match idb_bridge_for( + consumer_path: idb_consumer_source_path, + provider_paths: ["dag/test/fixture/import_deletion_bridge/never_declared.dag"], + subject_names: [idb_subject_leaf_name], + expected_references: 2, + ) { + IdbBridgeCandidateUnknown { file: f } => + f == "dag/test/fixture/import_deletion_bridge/never_declared.dag" + _ => false + } +} + +data idb_malformed_note: String = "A malformed provider source must surface as a parse refusal, not as an absent provider. Collapsing it to `no providers` would make an unparseable file indistinguishable from one that was never offered, and the remedies differ." + +test fn witness_malformed_provider_source_refuses() -> Bool { + match idb_bridge_for( + consumer_path: idb_consumer_source_path, + provider_paths: [idb_malformed_source_path], + subject_names: [idb_subject_name], + expected_references: 2, + ) { + IdbBridgeParseRefused { file: _ } => true + _ => false + } +} + +data idb_selection_note: String = "THE SELECTION REFUSAL. Asking for a reference count the fixture does not carry must refuse rather than proceed on whatever was found -- otherwise the multiplicity law restates its input instead of testing the projection." + +test fn witness_reference_count_mismatch_refuses() -> Bool { + match idb_bridge_for( + consumer_path: idb_consumer_source_path, + provider_paths: [idb_live_tree_source_path], + subject_names: [idb_subject_name], + expected_references: 3, + ) { + IdbBridgeSelectionRefused { observed: n } => n == 2 + _ => false + } +} + +data idb_ambiguity_note: String = "AMBIGUITY MUST NAME THE COMPETING DECLARATIONS. Two admissible providers each declare LiveTreeDisposition, so the reference has two candidates and cannot be bound. The requirement is not that the count is two -- two ambiguity outcomes with the same cardinality over different declarations are different states with different remedies -- so this asserts the EXACT declaration identities: the two candidate terminals differ from each other, and each is a declaration of this name present in the assembled transport. + +The identities are available because the carrier was repaired to keep them. resolve_reference_occurrence_binding already returned OccurrenceAmbiguous { occurrence, candidates } and the projection discarded the candidates with `candidates: _`; it now carries the upstream AmbiguousBindingCandidates itself, so this control reads production data rather than a local reconstruction." + +fn idb_declaration_occurrences_named( + transport: OccurrenceTransport, + name: String +) -> List { + fold(transport.declarations, init: idb_no_occurrence_ids(), f: (acc, declaration) => + match idb_index_entry_for_occurrence(transport: transport, occurrence: declaration.occurrence) { + Absent => acc + Present { value: entry } => + match entry.projection.authored_name == name { + true => concat(acc, [declaration.occurrence]) + false => acc + } + }) +} + +fn idb_no_occurrence_ids() -> List { [] } + +fn idb_occurrence_in(ids: List, id: OccurrenceId) -> Bool { + fold(ids, init: false, f: (found, candidate) => found || (candidate.value == id.value)) +} + +fn idb_ambiguity_candidates_for_two_providers() -> AmbiguousBindingCandidates? { + match idb_bridge_for( + consumer_path: idb_consumer_source_path, + provider_paths: [idb_live_tree_source_path, idb_second_provider_source_path], + subject_names: [idb_subject_name], + expected_references: 2, + ) { + IdbBridgeProjected { + projection: ReferenceDerivedDependencyProjectionBindingRefused { population: population }, + } => + match population { + ReferencePopulationRefused { first_failure: failure, more_failures: _ } => + match failure { + ReferenceBindingProjectionAmbiguous { occurrence: _, candidates: candidates } => + Present { value: candidates } + _ => none + } + _ => none + } + _ => none + } +} + +fn idb_two_provider_transport_declarations() -> List { + match first(idb_closure_row_for(path: idb_consumer_source_path, grounding: ModuleLocalMemberExposure)) { + Absent => idb_no_occurrence_ids() + Present { value: consumer_row } => + match assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: concat( + idb_closure_row_for(path: idb_live_tree_source_path, grounding: ModuleLocalMemberExposure), + idb_closure_row_for(path: idb_second_provider_source_path, grounding: ModuleLocalMemberExposure), + ), + ) { + AssembledCrossFileBindingClosureRefused { refusal: _ } => idb_no_occurrence_ids() + AssembledCrossFileBindingClosureReady { + transport: t, inputs: _, module_files: _, consumer_file: _, + } => idb_declaration_occurrences_named(transport: t, name: idb_subject_leaf_name) + } + } +} + +test fn witness_two_providers_refuse_as_ambiguous_naming_both_candidates() -> Bool { + match idb_ambiguity_candidates_for_two_providers() { + Absent => false + Present { value: candidates } => + let first_id = candidates.first.containment.terminal + let second_id = candidates.second.containment.terminal + let declared = idb_two_provider_transport_declarations() + !(first_id.value == second_id.value) + && idb_occurrence_in(ids: declared, id: first_id) + && idb_occurrence_in(ids: declared, id: second_id) + && length(declared) == 2 + } +} + +data idb_nested_rc_pattern_emitter_defect: String = "EMITTER DEFECT, located rather than routed around silently. The ambiguity control was first authored as one nested pattern -- ReferenceDerivedDependencyProjectionBindingRefused { population: ReferencePopulationRefused { first_failure } } -- which the v1 interpreter accepts and the Rust emitter lowers to `let ... = projection.as_ref()` matching an Rc field against a bare BoundReferencePopulation pattern (E0308, emitted at src/v1/stage0/src/v1_tests_claim_import_deletion_bridge_witness.rs). So a nested coproduct pattern through a reference-counted field does not survive emission, and regen --verify reports divergence 0 while the crate does not compile: byte-fidelity is not target-language validity. + +It is written here as two sequential matches, which is semantically identical and is the idiom the surrounding corpus already uses. That is NOT a repair, and it is recorded rather than left implicit because the class is the open Rc wrap-decision lane, whose whole subject is that the emitter decides Rc wrapping without a structural predicate. This control is not the place to change a load-bearing emitter. + +DISSOLVE-ON: the wrap-decision predicate lands and a nested pattern through an Rc field emits valid Rust; then this note deletes and the nesting may collapse back to one pattern." + +data idb_module_storage_note: String = "MODULE-TO-FILE CONTRADICTIONS MUST REFUSE, not resolve to one of the two answers. Two candidate files declare the SAME module path, so the module-to-file index has no single answer for it. That is a contradiction about storage, and either arm that catches it is a typed refusal -- what may never happen is a successful projection that silently picked a file, because the loader would then bring in one home and ignore the other. + +WHICH stage refuses is measured, not assumed: closure ASSEMBLY refuses, before the module-to-file index is ever built by this file, because assembly validates the accumulated rows itself. A first version of this control tolerated either that arm or an index refusal, which is a two-answer assertion -- it would have passed had the contradiction moved between the two stages, and it would not have said which stage held the line. Narrowing it to the arm that actually fires required a second repair of the same information-discarding class the ambiguity carrier had: this file was collapsing the assembly refusal to a bare marker, so the exact contradiction was unavailable to assert. It now carries AssembledCrossFileBindingClosureRefusal through, and this row names the module path and both competing files." + +test fn witness_conflicting_module_storage_refuses() -> Bool { + let bridge = idb_bridge_for( + consumer_path: idb_consumer_source_path, + provider_paths: [idb_live_tree_source_path, idb_conflicting_source_path], + subject_names: [idb_subject_name], + expected_references: 2, + ) + match bridge { + IdbBridgeAssemblyRefused { refusal: refusal } => + match refusal { + AssembledCrossFileBindingClosureModulePathFileRefused { refusal: index_refusal } => + match index_refusal { + ModulePathFileConflict { module_path: m, first_file: a, second_file: b } => + (m as String) == idb_provider_module_name + && !((a as String) == (b as String)) + && ((a as String) == idb_live_tree_source_path + || (a as String) == idb_conflicting_source_path) + && ((b as String) == idb_live_tree_source_path + || (b as String) == idb_conflicting_source_path) + _ => false + } + } + _ => false + } +} + +data idb_missing_storage_note: String = "A MISSING module-to-file row must surface as a file-projection refusal, not as a dropped edge. This control supplies the production projection with an index from which the provider module's row has been REMOVED, so the binding succeeds and only the storage join fails -- which is exactly the state where an implementation that silently omitted the edge would under-load the closure while reporting success." + +fn idb_index_without_provider_module() -> Map? { + match first(idb_closure_row_for(path: idb_consumer_source_path, grounding: ModuleLocalMemberExposure)) { + Absent => none + Present { value: consumer_row } => + match assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: idb_closure_row_for( + path: idb_live_tree_source_path, grounding: ModuleLocalMemberExposure, + ), + ) { + AssembledCrossFileBindingClosureRefused { refusal: _ } => none + AssembledCrossFileBindingClosureReady { + transport: _, inputs: _, module_files: rows, consumer_file: _, + } => + Present { + value: fold(rows, init: empty_map(), f: (acc, row) => + match (row.module_path as String) == idb_provider_module_name { + true => acc + false => map_insert(acc, row.module_path as String, row.file_path) + }), + } + } + } +} + +fn idb_population_for_supplied_provider() -> BoundReferencePopulation? { + match first(idb_closure_row_for(path: idb_consumer_source_path, grounding: ModuleLocalMemberExposure)) { + Absent => none + Present { value: consumer_row } => + match assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: idb_closure_row_for( + path: idb_live_tree_source_path, grounding: ModuleLocalMemberExposure, + ), + ) { + AssembledCrossFileBindingClosureRefused { refusal: _ } => none + AssembledCrossFileBindingClosureReady { + transport: t, inputs: i, module_files: _, consumer_file: _, + } => + match structural_binding_walk_selected_references( + transport: t, inputs: i, + references: idb_references_in_names(transport: t, names: [idb_subject_name]), + ) { + StructuralBindingWalkRefused { refusal: _ } => none + StructuralBindingWalkReady { population: population } => Present { value: population } + } + } + } +} + +test fn witness_missing_provider_module_storage_row_refuses_file_projection() -> Bool { + match idb_population_for_supplied_provider() { + Absent => false + Present { value: population } => + match idb_index_without_provider_module() { + Absent => false + Present { value: pruned } => + match reference_derived_dependency_projection( + population: population, module_files: pruned, + ) { + ReferenceDerivedDependencyProjectionFileRefused { + first_failure: _, more_failures: _, + } => true + _ => false + } + } + } +} + +data idb_join_note: String = "ITEM 4 -- THE TYPE-BINDING JOIN, and why it is a join rather than a filter flip. + +Clause (e) excludes TypeOccurrence for a stated reason: a parser-produced annotation such as `Int` would enter the population and refuse it whole, because std.types was never given to the closure. The wrong repairs are both available and both forbidden. Reclassifying TypeOccurrence as clause-(e)-inducing would make that clause say something its contract does not and would change every consumer of it silently. Dropping an unbindable type reference to keep the projection green would render `could not see the provider` as `nothing is depended on` -- the empty-observation narrow, worse than the widen DESIGN section 5 forbids, because a widen is merely expensive and a narrow is silently uncovered. + +So the admission surface has one arm per route and the walk folds both into ONE population through one shared candidate index. The controls below assert the three facts that distinguish a join from either wrong repair: both routes reach the same population, clause (e) itself is unchanged, and an unbindable type reference refuses by NAME." + +fn idb_type_consumer_source_path() -> String { + "dag/test/fixture/import_deletion_bridge/type_join_consumer.dag" +} + +fn idb_type_consumer_source() -> String { + "module test.fixture.import_deletion_bridge.type_join_consumer\n" + + "\n" + + "fn reads_disposition(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n" + + " d\n" + + "}\n" +} + +fn idb_joined_walk_for(consumer_path: String, provider_paths: List) -> StructuralBindingWalk? { + match first(idb_closure_row_for(path: consumer_path, grounding: ModuleLocalMemberExposure)) { + Absent => none + Present { value: consumer_row } => + match assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: fold(provider_paths, init: idb_no_closure_rows(), f: (acc, path) => + concat(acc, idb_closure_row_for( + path: path, grounding: ModuleLocalMemberExposure, + ))), + ) { + AssembledCrossFileBindingClosureRefused { refusal: _ } => none + AssembledCrossFileBindingClosureReady { + transport: t, inputs: i, module_files: _, consumer_file: _, + } => Present { value: joined_dependency_binding_walk(transport: t, inputs: i) } + } + } +} + +// CLAUSE (e) IS UNCHANGED. Stated as its own row because the whole point of adding an admission +// authority instead of editing the filter is that this stays true; a later commit that "simplified" +// the join by reclassifying the category would pass every other row here and fail this one. +test fn witness_clause_e_still_excludes_type_references() -> Bool { + match occurrence_category_clause_e_dependency_inducing_verdict(category: TypeOccurrence) { + OccurrenceCategoryClauseEDependencyNotInducing { category: _ } => true + OccurrenceCategoryClauseEDependencyInducing => false + } +} + +// THE ADMISSION TABLE, total over the closed coproduct. Asserting only the type arm would let a +// change that admitted fields or methods pass -- and a field reference admitted as dependency-inducing +// would make every record access look like a file dependency. +test fn witness_admission_routes_each_category_exactly_once() -> Bool { + idb_admits_clause_e(category: CallableOccurrence) + && idb_admits_clause_e(category: ConstructorOccurrence) + && idb_admits_clause_e(category: NamespaceSegmentOccurrence) + && idb_admits_clause_e(category: LexicalValueOccurrence) + && idb_admits_type(category: TypeOccurrence) + && idb_excludes(category: FieldOccurrence) + && idb_excludes(category: MethodOccurrence) +} + +fn idb_admits_clause_e(category: OccurrenceCategory) -> Bool { + match reference_dependency_admission_for_category(category: category) { + ReferenceDependencyAdmittedClauseE => true + ReferenceDependencyAdmittedTypeReference => false + ReferenceDependencyExcluded { category: _ } => false + } +} + +fn idb_admits_type(category: OccurrenceCategory) -> Bool { + match reference_dependency_admission_for_category(category: category) { + ReferenceDependencyAdmittedTypeReference => true + ReferenceDependencyAdmittedClauseE => false + ReferenceDependencyExcluded { category: _ } => false + } +} + +fn idb_excludes(category: OccurrenceCategory) -> Bool { + match reference_dependency_admission_for_category(category: category) { + ReferenceDependencyExcluded { category: observed } => observed == category + ReferenceDependencyAdmittedClauseE => false + ReferenceDependencyAdmittedTypeReference => false + } +} + +// THE PAIRED DISCRIMINATOR, and the reason it is measured at the EDGE and not at the population. +// +// The type-only consumer -- `fn reads_disposition(d: LiveTreeDisposition) -> LiveTreeDisposition` -- +// carries three references: two type annotations naming the cross-file subject, and one clause-(e) +// mention of its own parameter. So "clause (e) selects nothing here" is FALSE, and an earlier draft of +// this row asserted exactly that. What is true is the fact that matters: the one reference clause (e) +// admits is same-file, so the clause-(e) selection produces a Ready projection with ZERO file +// dependencies. That is the narrow in its purest form -- a successful answer of "this file depends on +// nothing" for a file whose signature names another file's type. +// +// The two rows are stated together because either alone is satisfiable by the wrong implementation: +// the zero row alone is satisfied by a projector that produces nothing at all, and the one-edge row +// alone is satisfied by a projector that always emits an edge to any supplied provider. +test fn witness_clause_e_alone_derives_no_edge_for_a_type_only_consumer() -> Bool { + match idb_clause_e_edges_for( + consumer_path: idb_type_consumer_source_path(), + provider_path: idb_live_tree_source_path, + ) { + Absent => false + Present { value: edges } => length(edges) == 0 + } +} + +test fn witness_joined_walk_derives_the_type_reference_edge() -> Bool { + match idb_joined_edges_for( + consumer_path: idb_type_consumer_source_path(), + provider_path: idb_live_tree_source_path, + ) { + Absent => false + Present { value: edges } => + length(edges) == 1 + && fold(edges, init: false, f: (found, edge) => + found || ((edge.provider_file as String) == idb_live_tree_source_path)) + } +} + +fn idb_clause_e_edges_for( + consumer_path: String, + provider_path: String +) -> List? { + match first(idb_closure_row_for(path: consumer_path, grounding: ModuleLocalMemberExposure)) { + Absent => none + Present { value: consumer_row } => + match assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: idb_closure_row_for( + path: provider_path, grounding: ModuleLocalMemberExposure, + ), + ) { + AssembledCrossFileBindingClosureRefused { refusal: _ } => none + AssembledCrossFileBindingClosureReady { + transport: t, inputs: i, module_files: rows, consumer_file: _, + } => + match module_path_file_index_from_rows(rows: rows) { + ModulePathFileIndexRefused { refusal: _ } => none + ModulePathFileIndexReady { entries: module_files } => + match structural_binding_walk_selected_references( + transport: t, inputs: i, + references: idb_clause_e_refs_for(path: consumer_path), + ) { + StructuralBindingWalkRefused { refusal: _ } => none + StructuralBindingWalkReady { population: population } => + match reference_derived_dependency_projection( + population: population, module_files: module_files, + ) { + ReferenceDerivedDependencyProjectionReady { + binding_provenance: _, file_dependencies: edges, bound_population: _, + } => Present { value: edges } + _ => none + } + } + } + } + } +} + +fn idb_joined_edges_for(consumer_path: String, provider_path: String) -> List? { + match first(idb_closure_row_for(path: consumer_path, grounding: ModuleLocalMemberExposure)) { + Absent => none + Present { value: consumer_row } => + match assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: idb_closure_row_for( + path: provider_path, grounding: ModuleLocalMemberExposure, + ), + ) { + AssembledCrossFileBindingClosureRefused { refusal: _ } => none + AssembledCrossFileBindingClosureReady { + transport: t, inputs: i, module_files: rows, consumer_file: _, + } => + match module_path_file_index_from_rows(rows: rows) { + ModulePathFileIndexRefused { refusal: _ } => none + ModulePathFileIndexReady { entries: module_files } => + match joined_dependency_binding_walk(transport: t, inputs: i) { + StructuralBindingWalkRefused { refusal: _ } => none + StructuralBindingWalkReady { population: population } => + match reference_derived_dependency_projection( + population: population, module_files: module_files, + ) { + ReferenceDerivedDependencyProjectionReady { + binding_provenance: _, file_dependencies: edges, bound_population: _, + } => Present { value: edges } + _ => none + } + } + } + } + } +} + +// ONE POPULATION, NOT TWO, at an exact count. Three references are admitted for this consumer -- two +// through the type route and one through clause (e) -- and all three must appear as providers in a +// SINGLE population. Two populations would each need their own totality law and could disagree about +// one file, which is the fork this join exists to prevent. The count is exact rather than a lower +// bound: a join that dropped one route would still satisfy any >= assertion. +test fn witness_both_routes_reach_one_population() -> Bool { + match idb_joined_walk_for( + consumer_path: idb_consumer_source_path, + provider_paths: [idb_live_tree_source_path], + ) { + Absent => false + Present { value: walk } => + match walk { + StructuralBindingWalkRefused { refusal: _ } => false + StructuralBindingWalkReady { population: population } => + match population { + AllReferencesBound { providers: providers } => + length(providers) == 3 + && length(idb_clause_e_refs_for(path: idb_consumer_source_path)) == 1 + ReferencePopulationRefused { first_failure: _, more_failures: _ } => false + } + } + } +} + +data idb_demand_note: String = "THE DEMAND, which is the whole reason the production switch waits. A type reference whose provider was never supplied must refuse and must NAME the occurrence it could not bind -- that named occurrence IS the loader's work list. A skip would leave the projection green and the closure short, with the deficit's frequency zeroed by construction so it never ranks for fixing." + +test fn witness_unsupplied_type_reference_refuses_by_name() -> Bool { + match idb_joined_walk_for( + consumer_path: idb_type_consumer_source_path(), + provider_paths: idb_no_strings(), + ) { + Absent => false + Present { value: walk } => + match walk { + StructuralBindingWalkRefused { refusal: _ } => false + StructuralBindingWalkReady { population: population } => + match population { + AllReferencesBound { providers: _ } => false + ReferencePopulationRefused { first_failure: failure, more_failures: _ } => + match failure { + ReferenceBindingProjectionUnbound { occurrence: occurrence } => + idb_named_occurrence_is( + consumer_path: idb_type_consumer_source_path(), + occurrence: occurrence, + name: idb_subject_name, + ) + _ => false + } + } + } + } +} + +fn idb_no_strings() -> List { [] } + +fn idb_named_occurrence_is(consumer_path: String, occurrence: OccurrenceId, name: String) -> Bool { + match idb_parsed_row(path: consumer_path) { + Absent => false + Present { value: row } => + match row { + ParsedOccurrenceBindingSourceRefused => false + ParsedOccurrenceBindingSourceReady { transport: t, module_path: _ } => + match idb_index_entry_for_occurrence(transport: t, occurrence: occurrence) { + Absent => false + Present { value: entry } => entry.projection.authored_name == name + } + } + } +} + +fn idb_clause_e_refs_for(path: String) -> List { + match idb_parsed_row(path: path) { + Absent => idb_no_references() + Present { value: row } => + match row { + ParsedOccurrenceBindingSourceRefused => idb_no_references() + ParsedOccurrenceBindingSourceReady { transport: t, module_path: _ } => + fold(t.references, init: idb_no_references(), f: (acc, r) => + match reference_dependency_admission_for_category(category: r.category) { + ReferenceDependencyAdmittedClauseE => concat(acc, [r]) + ReferenceDependencyAdmittedTypeReference => acc + ReferenceDependencyExcluded { category: _ } => acc + }) + } + } +} + +fn idb_all_refs_for(path: String) -> List { + match idb_parsed_row(path: path) { + Absent => idb_no_references() + Present { value: row } => + match row { + ParsedOccurrenceBindingSourceRefused => idb_no_references() + ParsedOccurrenceBindingSourceReady { transport: t, module_path: _ } => t.references + } + } +} + +data idb_reference_path_controls_note: String = "REFERENCE-PATH CONTROLS, on real parsed source rather than hand-built values. Each asserts a DIFFERENT decision of resolve_reference_path, which is what keeps them from being one claim written four ways: a path whose head and terminal both bind resolves to the TERMINAL declaration (not the module head, so the assertion names the terminal's own authored spelling); a path whose head binds and whose segment names nothing refuses AT THE SEGMENT, which is the discriminating red for the head-only widen this carrier deletes -- under that widen this case produced a ready dependency; a path whose head names nothing refuses at the HEAD, before any descent, so head and segment failures are distinguishable rather than one Unbound; and a head naming two modules refuses as head ambiguity BEFORE descent, so a nearest-match never gets the chance to pick. The resolved case is the positive control the three refusals are read against: without it, four refusals would be satisfiable by a resolver that refuses everything." + +fn idb_reference_path_closure() -> AssembledCrossFileBindingClosure? { + idb_reference_path_closure_with(providers: [idb_live_tree_source_path]) +} + +data idb_reference_path_provider_choice_note: String = "The provider SET is a parameter because two of these controls are about cardinality, and cardinality is a property of the assembled population rather than of the reference. Head ambiguity needs two modules whose last segment is live_tree; segment ambiguity needs one module declaring the member twice. Neither is expressible by changing the consumer text, which is why the closure takes its providers rather than naming one." + +fn idb_reference_path_closure_with(providers: List) -> AssembledCrossFileBindingClosure? { + match first(idb_closure_row_for(path: idb_path_consumer_source_path, grounding: ModuleLocalMemberExposure)) { + Absent => none + Present { value: consumer_row } => + Present { + value: assemble_cross_file_binding_closure( + consumer: consumer_row, + providers: fold(providers, init: idb_no_closure_rows(), f: (acc, path) => + concat(acc, idb_closure_row_for(path: path, grounding: ModuleLocalMemberExposure))), + ), + } + } +} + +fn idb_reference_path_resolution_for(name: String) -> ReferencePathResolution? { + idb_reference_path_resolution_in(name: name, providers: [idb_live_tree_source_path]) +} + +fn idb_reference_path_resolution_in(name: String, providers: List) -> ReferencePathResolution? { + match idb_reference_path_closure_with(providers: providers) { + Absent => none + Present { value: AssembledCrossFileBindingClosureRefused { refusal: _ } } => none + Present { value: AssembledCrossFileBindingClosureReady { transport: t, inputs: i, module_files: _, consumer_file: _ } } => + match occurrence_candidate_index_build(transport: t, inputs: i) { + OccurrenceCandidateIndexReady { index: index } => + fold(t.references, init: none, f: (found, r) => + match found { + Present { value: _ } => found + Absent => + match map_get(index.entries_by_id, r.occurrence.value) { + Absent => none + Present { value: entry } => + match entry.projection.authored_name == name { + false => none + true => Present { + value: resolve_reference_path(transport: t, index: index, reference: r), + } + } + } + }) + _ => none + } + } +} + +fn idb_resolved_terminal_name(name: String) -> String? { + match idb_reference_path_resolution_for(name: name) { + Absent => none + Present { value: ReferencePathResolved { terminal: terminal } } => + match idb_reference_path_authored_name_of(occurrence: terminal.occurrence) { + Absent => none + Present { value: spelled } => Present { value: spelled } + } + Present { value: _ } => none + } +} + +fn idb_reference_path_authored_name_of(occurrence: OccurrenceId) -> String? { + match idb_reference_path_closure() { + Absent => none + Present { value: AssembledCrossFileBindingClosureRefused { refusal: _ } } => none + Present { value: AssembledCrossFileBindingClosureReady { transport: t, inputs: _, module_files: _, consumer_file: _ } } => + match idb_index_entry_for_occurrence(transport: t, occurrence: occurrence) { + Absent => none + Present { value: entry } => Present { value: entry.projection.authored_name } + } + } +} + +test fn reference_path_resolves_to_the_terminal_declaration() -> Bool { + match idb_resolved_terminal_name(name: idb_subject_name) { + Absent => false + Present { value: spelled } => spelled == idb_subject_leaf_name + } +} + +test fn reference_path_missing_member_refuses_at_the_segment() -> Bool { + match idb_reference_path_resolution_for(name: "live_tree.NoSuchMember") { + Present { value: ReferencePathSegmentUnbound { segment: segment, parent: _ } } => + segment.name == "NoSuchMember" + _ => false + } +} + +test fn reference_path_missing_head_refuses_before_descent() -> Bool { + match idb_reference_path_resolution_for(name: "no_such_module.Anything") { + Present { value: ReferencePathHeadUnbound { head_name: head, span: _ } } => + head == "no_such_module" + _ => false + } +} + +data idb_duplicate_member_source_path: String = "dag/test/fixture/import_deletion_bridge/duplicate_member_live_tree.dag" + +data idb_duplicate_member_source_note: String = "One module declaring the SAME member spelling twice. This is not a well-formed program and is not meant to be: the subject is what the path resolver does when a segment has two candidates, and the answer must be a refusal naming both rather than a first-wins pick. Authored as its own fixture so no well-formed provider has to be corrupted to ask the question." + +fn idb_duplicate_member_source() -> String { + "module v2.std.duplicated\n\ntype Twice\n = TwiceOne\n | TwiceTwo\n\ntype Twice\n = TwiceThree\n | TwiceFour\n" +} + +test fn reference_path_two_modules_named_alike_refuse_at_the_head() -> Bool { + match idb_reference_path_resolution_in( + name: idb_subject_name, + providers: [idb_live_tree_source_path, idb_second_provider_source_path], + ) { + Present { value: ReferencePathHeadAmbiguous { head_name: head, span: _, candidates: candidates } } => + (head == "live_tree") && (length(candidates) == 2) + _ => false + } +} + +test fn reference_path_ordinary_record_field_is_not_a_namespace_projection() -> Bool { + match idb_reference_path_resolution_for(name: "ordinary_field") { + Present { value: ReferencePathNotANamespaceProjection { head: head } } => + match head.category { + NamespaceSegmentOccurrence => false + _ => true + } + _ => false + } +} + +test fn reference_path_duplicated_member_refuses_at_the_segment() -> Bool { + match idb_reference_path_resolution_in( + name: "duplicated.Twice", + providers: [idb_live_tree_source_path, idb_duplicate_member_source_path], + ) { + Present { value: ReferencePathSegmentAmbiguous { segment: segment, parent: _, candidates: candidates } } => + (segment.name == "Twice") && (length(candidates) == 2) + _ => false + } +} + +data idb_multi_segment_note: String = "MULTI-SEGMENT DESCENT, and the shape that makes it constructible from ordinary .dag source. Modules are flat per file, so a.b.c cannot be module-inside-module; but a module member CAN have members -- live_tree.LiveTreeDisposition.ReadsLiveTree is module, then type, then variant -- so three segments is two real containment steps rather than a synthetic nesting. The fully qualified spelling is asserted beside it because both are answered by ONE rule, the longest prefix naming a module: the exact-path match serves v2.std.live_tree and the suffix match serves live_tree, so neither spelling needs its own resolution route." + +test fn reference_path_descends_three_segments_to_the_variant() -> Bool { + match idb_resolved_terminal_name(name: "live_tree.LiveTreeDisposition.ReadsLiveTree") { + Present { value: spelled } => spelled == "ReadsLiveTree" + Absent => false + } +} + +test fn reference_path_resolves_the_fully_qualified_spelling() -> Bool { + match idb_resolved_terminal_name(name: "v2.std.live_tree.LiveTreeDisposition") { + Present { value: spelled } => spelled == idb_subject_leaf_name + Absent => false + } +} diff --git a/src/v1/tests/claim/occurrence_binding_parser_walk_witness_test.dag b/src/v1/tests/claim/occurrence_binding_parser_walk_witness_test.dag index 01b6bf2f26d..7b703ab835e 100644 --- a/src/v1/tests/claim/occurrence_binding_parser_walk_witness_test.dag +++ b/src/v1/tests/claim/occurrence_binding_parser_walk_witness_test.dag @@ -12,6 +12,7 @@ import std.occurrence_identity { AuthoredTokenOrdinal, } import std.occurrence_binding_candidates { + ModuleLocalMemberExposure, OccurrenceBindingCandidateInputs, DeclarationExposureRow, LexicalExposure, @@ -69,6 +70,7 @@ fn obcpw_parse(file: String, source: String) -> ObcpwParsed { inputs: occurrence_binding_inputs_from_transport( module_path: module_path, transport: transport, + grounding: ModuleLocalMemberExposure, ), } } diff --git a/src/v1/tests/claim/parser_declaration_category_matrix_witness_test.dag b/src/v1/tests/claim/parser_declaration_category_matrix_witness_test.dag new file mode 100644 index 00000000000..d5dc417f08e --- /dev/null +++ b/src/v1/tests/claim/parser_declaration_category_matrix_witness_test.dag @@ -0,0 +1,218 @@ +module v1.tests.claim.parser_declaration_category_matrix_witness_test + +import std.types { Bool } +import std.occurrence_identity { DeclarationOccurrence } +import v1.tests.claim.parser_declaration_matrix_support { + pdc_row, + pdc_zero_row, + pdc_identity_row, + pdc_parse_matrix, + pdc_declares_nothing, + pdc_index_entry_for_declaration, + pdc_matrix_category_count, + pdc_reference_row, + pdc_reference_row, + pdc_non_type_category_count, + PdcTransportReady, + PdcTransportRefused, +} + +data pdc_matrix_witness_note: String = "Parser declaration-category denominator. Pins, per authored declaration form, the OccurrenceCategory population the v1 parser mints — including the forms that mint nothing. Observation-only: no resolution, exposure, or binding claim. The zero rows are the load-bearing half; minting a category later must edit this file." + +data pdc_offline_recipe: String = "OFFLINE LOCAL RECIPE (requires v1 parser pool): target/release/claim_batch --roster-from-discovery --source-root dag --source-root src/v1 --scan-dir src/v1/tests/claim. The function list is deliberately NOT enumerated here: this file is enrolled file-grain, so the empty-function authority expands it, and a hand-copied list of test fn names beside a file that already names them is a second representation that rots on the next rename." + +data pdc_dissolve_on: String = "DISSOLVE-ON: witness_layer_roots admits src/v1 for parser-transport witnesses, or the execution corpus carries per-entry source roots — then this recipe deletes and discovery enrollment suffices." + +// One module carrying every declaration form the matrix ranges over, each with a distinct authored +// name so a per-name join is exact. Named `pdc_*` throughout: an authored name that collided with a +// real corpus declaration would make the join ambiguous against a homonym rather than wrong-looking. + +// POSITIVE CONTROL. Without it, every "mints nothing" row below would pass vacuously if the fixture +// stopped parsing, and the matrix would describe a parser that mints nothing at all. +test fn witness_matrix_fixture_parses() -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + length(transport.declarations) > 0 && length(transport.references) > 0 + } +} + +// --------------------------------------------------------------------------------------------- +// Derived requirements the parser ALREADY meets. These are regression controls: each is required by +// the language, and each happens to hold today, so a commit C that breaks one is caught. +// --------------------------------------------------------------------------------------------- + +// A function declaration introduces a callable name into its module. +test fn witness_function_declaration_mints_callable() -> Bool { + pdc_row(name: "pdc_function", expected: "|CallableOccurrence") +} + +test fn witness_second_function_declaration_mints_callable() -> Bool { + pdc_row(name: "pdc_matcher", expected: "|CallableOccurrence") +} + +// A parameter binds a name in the function body's scope, so it is a declaration. +test fn witness_function_parameter_binds() -> Bool { + pdc_row(name: "pdc_parameter", expected: "|LexicalValueOccurrence") +} + +test fn witness_match_scrutinee_parameter_binds() -> Bool { + pdc_row(name: "pdc_scrutinee", expected: "|LexicalValueOccurrence") +} + +// A let introduces a binding. +test fn witness_let_binder_binds() -> Bool { + pdc_row(name: "pdc_let_binder", expected: "|LexicalValueOccurrence") +} + +// A destructuring pattern binds its binder name into the arm's scope. +test fn witness_pattern_binder_binds() -> Bool { + pdc_row(name: "pdc_pattern_binder", expected: "|LexicalValueOccurrence") +} + +// --------------------------------------------------------------------------------------------- +// LANDED BY COMMIT C. These eight rows were authored as deliberately-red acceptance targets in a +// separate file, because this roster is uniformly file-grain and an enrolled file cannot mix green +// with expected-red. Role-directed minting in `stamp_parsed_node_children` greened them, so they MOVE +// here rather than retiring: the requirement they state did not stop being a requirement when it +// started holding, and deleting the discriminating evidence with the machinery would recreate +// specification-without-execution one rung up (DESIGN section 4b(4)). +// --------------------------------------------------------------------------------------------- + +// A named type declaration introduces a type name into its module. Both the product and the +// coproduct form must; a type ALIAS must too, and is a third shape (NoConnective with `inferred` +// present) that a connective-keyed rule would miss. +test fn witness_product_type_declaration_is_declared() -> Bool { + pdc_row(name: "PdcProduct", expected: "|TypeOccurrence") +} + +test fn witness_coproduct_type_declaration_is_declared() -> Bool { + pdc_row(name: "PdcSum", expected: "|TypeOccurrence") +} + +// A variant arm introduces a constructor name, whether or not it carries a payload. Both forms +// follow the same rule; asserting only the payload form would let a nullary-only regression pass. +test fn witness_nullary_constructor_is_declared() -> Bool { + pdc_row(name: "PdcNullary", expected: "|ConstructorOccurrence") +} + +test fn witness_payload_constructor_is_declared() -> Bool { + pdc_row(name: "PdcPayload", expected: "|ConstructorOccurrence") +} + +// A product type's field is declared by the type that owns it. +test fn witness_product_field_is_declared() -> Bool { + pdc_row(name: "pdc_product_field", expected: "|FieldOccurrence") +} + +// THE TWO ROWS THAT A NAME JOIN CANNOT STATE, and the reason this file keys on identity. +// +// `pdc_payload_field` is authored twice: at byte 109 as the coproduct's payload FIELD DECLARATION, +// and at byte 394 as the field position of the destructuring pattern. The second is a USE of the +// first — it names which field is being destructured; the binder is `pdc_pattern_binder`. So the +// language requires exactly one DECLARATION of this name, at 109, and the pattern position must not +// produce one. +// +// Today the parser does the opposite: the only declaration it mints sits at 394, and 109 mints +// nothing. Because both readings yield a population of size one, a count or a name assertion cannot +// tell them apart — which is why the earlier version of this file passed while describing the wrong +// occurrence. The spans are hard-coded deliberately: this fixture authors its own source, and a +// fixture edit that moves these positions SHOULD break these rows rather than silently re-point them. +test fn witness_coproduct_payload_field_is_declared_at_its_declaration_site() -> Bool { + pdc_identity_row( + name: "pdc_payload_field", + expected: "|pdc_payload_field#FieldOccurrence#span:109-126#path:>app.pdc_matrix>PdcSum>PdcPayload", + ) +} + +// Stated separately from the row above so the two defects are counted separately: even once the +// declaration at 109 exists, a parser that ALSO kept minting a declaration at 394 would still be +// wrong, and the row above alone would not say so. +test fn witness_pattern_field_position_is_not_a_declaration() -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + fold( + transport.declarations, + init: true, + f: (acc, declaration) => + match pdc_index_entry_for_declaration(transport: transport, declaration: declaration) { + Absent => acc + Present { value: entry } => + acc && entry.projection.diagnostic_span.start != 394 + }, + ) + } +} + +// THE THIRD ROW, so the two defects are independently discriminated. +// +// The population row above bundles two facts -- that a declaration exists at 109, and that nothing +// else carries this name -- so its failure cannot say which held. The sibling row isolates the +// second defect (a declaration at 394). This row isolates the FIRST: a declaration exists at the +// coproduct's declaration site, said without any claim about what else exists. +// +// With all three, commit C cannot green by fixing one defect and masking the other: this row fails +// if the declaration at 109 is still missing, the sibling fails if the use at 394 is still minted as +// a declaration, and the population row fails if a third occurrence of the name appears anywhere. +test fn witness_a_declaration_exists_at_the_coproduct_field_site() -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + fold( + transport.declarations, + init: false, + f: (found, declaration) => + match pdc_index_entry_for_declaration(transport: transport, declaration: declaration) { + Absent => found + Present { value: entry } => + found || entry.projection.diagnostic_span.start == 109 + }, + ) + } +} + +// --------------------------------------------------------------------------------------------- +// THE FALSE-POSITIVE DENOMINATOR. Deciding that an item binds a type name requires excluding every +// item form that does not, and the rows above only exercise the two that do. These two rows are a +// self-denominating pair over two fixtures: the matrix authors exactly two named type declarations, +// and the sibling fixture authors none, so a rule that over-mints fails the second and a rule that +// stops minting fails the first. Neither can pass by the counter being vacuously zero. +// --------------------------------------------------------------------------------------------- + +// THREE, not two: the fixture authors a product, a coproduct and an ALIAS. The alias is the third +// shape and the one a connective-keyed rule misses -- `type PdcAlias = PdcProduct` carries +// NoConnective with an `inferred` right-hand side, so a rule keyed on the connective alone mints +// nothing for it while still passing both rows above. Asserting the count without it would leave that +// hole undetectable from this file. +test fn witness_matrix_declares_exactly_its_three_named_types() -> Bool { + pdc_matrix_category_count(tag: "TypeOccurrence") == 3 +} + +test fn witness_type_alias_declaration_is_declared() -> Bool { + pdc_row(name: "PdcAlias", expected: "|TypeOccurrence") +} + +// THE POSITIVE HALF of the pattern-field pair. Its sibling asserts that no DECLARATION sits at 394; +// on its own that is satisfied equally by a correct reference and by nothing being minted there at +// all, which would leave the field position unresolvable and silently drop the edge it induces. This +// asserts the occurrence that must exist: a FieldOccurrence REFERENCE at 394-411, carrying an +// identity distinct from the declaration at 109 that its sibling row pins, and a containment chain +// that locates it inside the matching function rather than inside the type. +test fn witness_pattern_field_position_is_a_field_reference() -> Bool { + pdc_reference_row( + name: "pdc_payload_field", + expected: "|pdc_payload_field#FieldOccurrence#span:394-411#path:>app.pdc_matrix>pdc_matcher>>", + ) +} + +test fn witness_no_non_type_item_form_becomes_a_type_declaration() -> Bool { + pdc_non_type_category_count(tag: "TypeOccurrence") == 0 +} + +// POSITIVE CONTROL for the row above: without it, a fixture that stopped parsing would satisfy the +// zero claim. A refused parse answers -1 rather than 0 for the same reason, so the claim is +// fail-closed on both the parse and the population. +test fn witness_non_type_item_fixture_declares_its_callables() -> Bool { + pdc_non_type_category_count(tag: "CallableOccurrence") == 2 +} diff --git a/src/v1/tests/claim/parser_declaration_matrix_support.dag b/src/v1/tests/claim/parser_declaration_matrix_support.dag new file mode 100644 index 00000000000..b8f941d51dc --- /dev/null +++ b/src/v1/tests/claim/parser_declaration_matrix_support.dag @@ -0,0 +1,455 @@ +module v1.tests.claim.parser_declaration_matrix_support + + +import std.types { Bool, String, List, Int, NonEmptyStr } +import std.occurrence_identity { + OccurrenceId, + OccurrenceTransport, + ReferenceOccurrence, + OccurrenceIndexEntry, + DeclarationOccurrence, + OccurrenceCategory, + LexicalValueOccurrence, + TypeOccurrence, + CallableOccurrence, + ConstructorOccurrence, + NamespaceSegmentOccurrence, + FieldOccurrence, + MethodOccurrence, + ModuleValueOccurrence, +} +import v1.gunbc.occurrence_binding_parser_walk { + ParsedOccurrenceBindingSourceRefused, + ParsedOccurrenceBindingSourceReady, + parse_authored_occurrence_binding_source, +} + +// THE SINGLE SUPPORT AUTHORITY for the parser declaration-category matrix. +// +// Extracted because the matrix is asserted from TWO witness files -- the enrolled green one and its +// expected-red sibling -- and src/v1 witnesses are reachable only through the v1 claim scoped +// roster, which witness_v1_claim_scoped_batch_is_file_grain_and_batch_owned requires to be +// uniformly file-grain. So an enrolled file must be all-green and deliberately-red rows must live +// separately. That split is necessary; duplicating the instrument across it was not, and briefly +// happened: both files carried the same fixture, transport parse, category projection and identity +// rendering under two prefixes, which is one instrument with two representations and exactly the +// thing DESIGN section 2/3 forbids. This module is that single authority; neither witness file +// declares any of it. +data pdc_support_authority_note: String = "Single support authority for the parser declaration-category matrix, imported by both the enrolled green witness and its expected-red sibling. Owns the fixture source, the transport parse, the category projection, and the exact-identity rendering (authored name, category, byte span, containment chain). Declares no test fn and asserts nothing: the requirements live in the witness files, the instrument lives here once." + +fn pdc_matrix_source() -> String { + "module app.pdc_matrix\n" + + "\n" + + "type PdcProduct {\n" + + " pdc_product_field: Int\n" + + "}\n" + + "\n" + + "type PdcSum = PdcNullary | PdcPayload { pdc_payload_field: Int }\n" + + "\n" + + "data pdc_annotated_datum: Int = 7\n" + + "\n" + + "fn pdc_function(pdc_parameter: Int) -> Int {\n" + + " let pdc_let_binder = pdc_parameter\n" + + " pdc_let_binder\n" + + "}\n" + + "\n" + + "fn pdc_matcher(pdc_scrutinee: PdcSum) -> Int {\n" + + " match pdc_scrutinee {\n" + + " PdcNullary => {\n" + + " 0\n" + + " }\n" + + " PdcPayload { pdc_payload_field: pdc_pattern_binder } => {\n" + + " pdc_pattern_binder\n" + + " }\n" + + " }\n" + + "}\n" + + "\n" + + "type PdcAlias = PdcProduct\n" +} + +type PdcTransport + = PdcTransportReady { transport: OccurrenceTransport } + | PdcTransportRefused + +fn pdc_parse_matrix() -> PdcTransport { + match parse_authored_occurrence_binding_source( + file: "app/pdc_matrix.dag", + source: pdc_matrix_source(), + ) { + ParsedOccurrenceBindingSourceRefused => PdcTransportRefused + ParsedOccurrenceBindingSourceReady { transport: transport, module_path: _ } => + PdcTransportReady { transport: transport } + } +} + +// A total projection of the closed OccurrenceCategory coproduct onto a comparable tag. It is a +// projection rather than a Bool predicate per category so that an assertion names the category the +// parser ACTUALLY minted when it disagrees, instead of reporting only that some expectation failed. +fn pdc_category_tag(category: OccurrenceCategory) -> String { + match category { + LexicalValueOccurrence => "LexicalValueOccurrence" + TypeOccurrence => "TypeOccurrence" + CallableOccurrence => "CallableOccurrence" + ConstructorOccurrence => "ConstructorOccurrence" + NamespaceSegmentOccurrence => "NamespaceSegmentOccurrence" + FieldOccurrence => "FieldOccurrence" + MethodOccurrence => "MethodOccurrence" + ModuleValueOccurrence => "ModuleValueOccurrence" + } +} + +fn pdc_index_entry_for_declaration( + transport: OccurrenceTransport, + declaration: DeclarationOccurrence +) -> OccurrenceIndexEntry? { + fold( + transport.index.entries, + init: none, + f: (found, entry) => + match found { + Present { value: _ } => found + Absent => + match entry.projection.occurrence.value == declaration.occurrence.value { + true => Present { value: entry } + false => Absent + } + }, + ) +} + +// Every category tag the parser minted for declarations carrying this authored name, in transport +// order. A name the parser never declared yields the empty list — which is the assertion the zero +// rows below make, and the reason this returns a population rather than an Optional. +fn pdc_declared_tags_for_name( + transport: OccurrenceTransport, + name: String +) -> List { + fold( + transport.declarations, + init: [], + f: (acc, declaration) => + match pdc_index_entry_for_declaration(transport: transport, declaration: declaration) { + Absent => acc + Present { value: entry } => + match entry.projection.authored_name == name { + true => concat(acc, [pdc_category_tag(category: declaration.category)]) + false => acc + } + }, + ) +} + +// The minted population rendered as one ordered, delimited string. Comparing the rendering rather +// than walking two lists in parallel keeps ORDER and MULTIPLICITY in the comparison: a name minted +// twice, or minted in the other order, is a different string. The leading delimiter is what makes +// that true at the edges — without it "A" would be a prefix-collision hazard against "AB". +fn pdc_render_tags(tags: List) -> String { + fold(tags, init: "", f: (acc, tag) => acc + "|" + tag) +} + +// EXACT OCCURRENCE IDENTITY, because an authored name is not one. +// +// `pdc_payload_field` is authored TWICE in the fixture — once as the coproduct's payload field, once +// as the field position of the destructuring pattern — and that collision is deliberately kept +// rather than renamed away, because it is what the real language does. A name-keyed assertion +// cannot say WHICH of the two it matched: a single-tag expectation passes whether the parser minted +// the declaration-side field or the pattern-side one, so the row silently reports the wrong subject +// half the time. The parser repair is about exact occurrences and its witness must not collapse +// them back into a name census. +// +// So every row below is keyed on the tuple the occurrence actually carries: authored name, category, +// byte span, and containment depth plus the authored name of the immediately containing occurrence. +// Multiplicity is carried by rendering the whole matching POPULATION rather than a first match. + +// The containment path rendered as the chain of authored ancestor names, root first. +// +// A chain rather than a depth integer, because "containment path" is what the occurrence carries and +// the chain is what discriminates: it separates the coproduct's own payload field (…>PdcSum>PdcPayload) +// from the pattern's field position (…>pdc_matcher>…), and it is what commit C's control means when +// it requires a variant's containment to NAME its owning type. A depth number cannot state either. +// An ancestor whose node has no authored name renders as "" rather than being skipped, so an +// anonymous link in the chain is visible instead of silently shortening it. +fn pdc_containment_chain( + transport: OccurrenceTransport, + declaration: DeclarationOccurrence +) -> String { + fold( + declaration.containment.ancestors, + init: "", + f: (chain, ancestor) => + chain + ">" + pdc_named_or_anon( + transport: transport, + occurrence_value: ancestor.value, + ), + ) +} + +fn pdc_named_or_anon(transport: OccurrenceTransport, occurrence_value: Int) -> String { + let found = pdc_name_for_occurrence(transport: transport, occurrence_value: occurrence_value) + match found == "" { + true => "" + false => found + } +} + +fn pdc_name_for_occurrence(transport: OccurrenceTransport, occurrence_value: Int) -> String { + fold( + transport.index.entries, + init: "", + f: (found, entry) => + match found == "" { + false => found + true => + match entry.projection.occurrence.value == occurrence_value { + true => entry.projection.authored_name + false => found + } + }, + ) +} + +fn pdc_render_occurrence( + transport: OccurrenceTransport, + declaration: DeclarationOccurrence, + entry: OccurrenceIndexEntry +) -> String { + entry.projection.authored_name + + "#" + pdc_category_tag(category: declaration.category) + + "#span:" + to_string(entry.projection.diagnostic_span.start) + + "-" + to_string(entry.projection.diagnostic_span.end) + + "#path:" + pdc_containment_chain(transport: transport, declaration: declaration) +} + +// Every declaration carrying this authored name, rendered at full identity, in transport order. +// A name minted twice yields two rows, so multiplicity is asserted rather than assumed. +fn pdc_identity_rows_for_name( + transport: OccurrenceTransport, + name: String +) -> List { + fold( + transport.declarations, + init: [], + f: (acc, declaration) => + match pdc_index_entry_for_declaration(transport: transport, declaration: declaration) { + Absent => acc + Present { value: entry } => + match entry.projection.authored_name == name { + true => + concat(acc, [pdc_render_occurrence( + transport: transport, + declaration: declaration, + entry: entry, + )]) + false => acc + } + }, + ) +} + +fn pdc_identity_population(transport: OccurrenceTransport, name: String) -> String { + pdc_render_tags(tags: pdc_identity_rows_for_name(transport: transport, name: name)) +} + +fn pdc_declares_exactly( + transport: OccurrenceTransport, + name: String, + expected: String +) -> Bool { + pdc_render_tags(tags: pdc_declared_tags_for_name(transport: transport, name: name)) == expected +} + +// THE REFERENCES SIDE of the same instrument. A declaration population alone cannot state that an +// occurrence is a USE: "no declaration carries this name at span 394" is satisfied both by a correct +// reference and by nothing at all being minted there. Rendering references at full identity is what +// makes the positive half of that pair assertable. +fn pdc_reference_rows_for_name( + transport: OccurrenceTransport, + name: String +) -> List { + fold( + transport.references, + init: [], + f: (acc, reference) => + match pdc_index_entry_for_occurrence(transport: transport, occurrence_value: reference.occurrence.value) { + Absent => acc + Present { value: entry } => + match entry.projection.authored_name == name { + true => + concat(acc, [entry.projection.authored_name + + "#" + pdc_category_tag(category: reference.category) + + "#span:" + to_string(entry.projection.diagnostic_span.start) + + "-" + to_string(entry.projection.diagnostic_span.end) + + "#path:" + pdc_reference_chain(transport: transport, reference: reference)]) + false => acc + } + }, + ) +} + +fn pdc_reference_chain( + transport: OccurrenceTransport, + reference: ReferenceOccurrence +) -> String { + fold( + reference.containment.ancestors, + init: "", + f: (chain, ancestor) => + chain + ">" + pdc_named_or_anon(transport: transport, occurrence_value: ancestor.value), + ) +} + +fn pdc_index_entry_for_occurrence( + transport: OccurrenceTransport, + occurrence_value: Int +) -> OccurrenceIndexEntry? { + fold( + transport.index.entries, + init: none, + f: (found, entry) => + match found { + Present { value: _ } => found + Absent => + match entry.projection.occurrence.value == occurrence_value { + true => Present { value: entry } + false => Absent + } + }, + ) +} + +fn pdc_reference_population(transport: OccurrenceTransport, name: String) -> String { + pdc_render_tags(tags: pdc_reference_rows_for_name(transport: transport, name: name)) +} + +fn pdc_reference_row(name: String, expected: String) -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + pdc_reference_population(transport: transport, name: name) == expected + } +} + +fn pdc_declares_nothing(transport: OccurrenceTransport, name: String) -> Bool { + length(pdc_declared_tags_for_name(transport: transport, name: name)) == 0 +} + + +// THE MATRIX — every row is a DERIVED requirement, none is a copied measurement. +// +// The expected value of each row comes from what the .dag language says must bind, never from what +// the current parser happens to produce. That distinction is the whole design of this file. Writing +// the expectations by observing the tree would make the fixture a change detector — it would report +// "the parser still does what the parser does" and go green over defects — which is exactly the +// tree-copied-oracle failure DESIGN section 5 rules out. The fixture is controlled (this file +// authors its own input), so deriving the expected population is possible and is therefore required. +// +// A consequence, stated plainly because it is unusual: SEVERAL ROWS BELOW ARE EXPECTED TO FAIL +// TODAY. They are the derived requirements the v1 parser does not yet meet, and they are the +// acceptance targets commit C must flip. They are enrolled function-grain, never file-grain, so a +// deliberate red is never confused with a broken batch. +// +// One row per requirement rather than a conjunction, so a failure names the form that disagreed. + +fn pdc_row(name: String, expected: String) -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + pdc_declares_exactly(transport: transport, name: name, expected: expected) + } +} + +fn pdc_zero_row(name: String) -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + pdc_declares_nothing(transport: transport, name: name) + } +} + +fn pdc_identity_row(name: String, expected: String) -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + pdc_identity_population(transport: transport, name: name) == expected + } +} + +// THE FALSE-POSITIVE DENOMINATOR for type-declaration minting. +// +// Minting a type declaration requires deciding, of a module item, whether it binds a type name. The +// matrix fixture above only proves the two forms that DO are minted; on its own it says nothing about +// the forms that do not, and the shapes are close enough to collide: a product FIELD node carries an +// `inferred` type with a Present ident_span, a payload VARIANT node carries the same `connective: +// Conj` a product declaration does, and every module item form other than `type` has to be excluded +// by something. This module authors ONLY non-type item forms, so the whole-population claim is +// available and needs no per-name join: the transport must carry zero type declarations. +// +// `service` and `resource` are deliberately absent -- they are excluded by carrying a transport, an +// independent axis from the body/connective test, and authoring one here would be authoring the +// dependency's shape rather than the language's. That residue is named, not silently dropped. +fn pdc_non_type_item_source() -> String { + "module app.pdc_non_type\n" + + "\n" + + "data pdc_nt_datum: Int = 7\n" + + "\n" + + "fn pdc_nt_fn(pdc_nt_param: Int) -> Int {\n" + + " pdc_nt_param\n" + + "}\n" + + "\n" + + "func pdc_nt_func(pdc_nt_func_param: Int) -> Int {\n" + + " pdc_nt_func_param\n" + + "}\n" +} + +fn pdc_parse_non_type_items() -> PdcTransport { + match parse_authored_occurrence_binding_source( + file: "app/pdc_non_type.dag", + source: pdc_non_type_item_source(), + ) { + ParsedOccurrenceBindingSourceRefused => PdcTransportRefused + ParsedOccurrenceBindingSourceReady { transport: transport, module_path: _ } => + PdcTransportReady { transport: transport } + } +} + +fn pdc_declared_category_count( + transport: OccurrenceTransport, + tag: String +) -> Int { + fold( + transport.declarations, + init: 0, + f: (acc, declaration) => + match pdc_category_tag(category: declaration.category) == tag { + true => acc + 1 + false => acc + }, + ) +} + +fn pdc_matrix_category_count(tag: String) -> Int { + match pdc_parse_matrix() { + PdcTransportRefused => 0 - 1 + PdcTransportReady { transport: transport } => + pdc_declared_category_count(transport: transport, tag: tag) + } +} + +fn pdc_non_type_category_count(tag: String) -> Int { + match pdc_parse_non_type_items() { + PdcTransportRefused => 0 - 1 + PdcTransportReady { transport: transport } => + pdc_declared_category_count(transport: transport, tag: tag) + } +} + +// Population SIZE for one authored name. Separate from pdc_row because a requirement may be certain +// about multiplicity while the category it should carry is still an open modelling question; keying +// such a row on a category would decide that question inside the assertion. +fn pdc_declaration_count_row(name: String, expected: Int) -> Bool { + match pdc_parse_matrix() { + PdcTransportRefused => false + PdcTransportReady { transport: transport } => + length(pdc_declared_tags_for_name(transport: transport, name: name)) == expected + } +} diff --git a/src/v1/tests/claim/reference_derived_closure_witness_test.dag b/src/v1/tests/claim/reference_derived_closure_witness_test.dag new file mode 100644 index 00000000000..74c85486cee --- /dev/null +++ b/src/v1/tests/claim/reference_derived_closure_witness_test.dag @@ -0,0 +1,502 @@ +module v1.tests.claim.reference_derived_closure_witness_test + +import std.types { Bool, String, List, Int, NonEmptyStr, FilePath } +import std.occurrence_identity { OccurrenceTransport, OccurrenceId, OccurrenceProjection } +import std.occurrence_binding_candidates { + CrossFileBindingClosureRow, + cross_file_binding_closure_row, + BoundReferencePopulation, + ReferencePopulationRefused, + ReferenceBindingProjectionUnbound, + ReferenceBindingProjectionAmbiguous, + DirectFileDependency, + CrossFileBindingProvenance, + DeclarationExposureGrounding, + ModuleLocalMemberExposure, +} +import std.reference_derived_closure { + ReferenceDerivedClosureState, + ParsedFileSupply, + ReferenceDerivedClosureStep, + ClosureStepAdvanced, + ClosureStepSettled, + ClosureStepRefused, + ClosureNoEntryAdmitted, + ClosureBindingRefused, + ReferenceDerivedClosureFixedPoint, + ClosureFixedPointSettled, + ClosureFixedPointRefused, + ClosureFixedPointExhausted, + reference_derived_closure_step, + reference_derived_closure_initial, + reference_derived_closure_fixed_point, +} +import v1.gunbc.occurrence_binding_parser_walk { + ParsedOccurrenceBindingSourceReady, + ParsedOccurrenceBindingSourceRefused, + parse_authored_occurrence_binding_source, + occurrence_binding_inputs_from_transport, +} + +data rdc_note: String = "THE LOADER FIXED POINT over the ordinary source-file universe. + +TWO POPULATIONS, NOT ONE. The candidate index is built over the WHOLE universe of parsed source files; the references BOUND are selected by the file they belong to, from the admitted closure only. That separation is what makes discovery possible: the declaration that a bare reference binds to is visible before the file carrying it is in the closure, and the edge derived from that binding is what admits the file. Collapsing the two deadlocks -- an unadmitted provider has no declaration in the index, so the reference is Unbound, so no provider file is derived, so nothing is ever admitted. + +A module-to-file index cannot substitute: storage realization says where a module lives and never which module declares a name." + +data rdc_consumer_path: String = "dag/test/fixture/reference_derived_closure/consumer.dag" +data rdc_provider_path: String = "src/v2/std/live_tree.dag" +data rdc_second_provider_path: String = "dag/test/fixture/reference_derived_closure/second_provider.dag" +data rdc_unrelated_path: String = "dag/test/fixture/reference_derived_closure/unrelated.dag" +data rdc_consumer_module: String = "test.fixture.reference_derived_closure.consumer" +data rdc_later_path: String = "dag/test/fixture/reference_derived_closure/later.dag" +data rdc_later_consumer_path: String = "dag/test/fixture/reference_derived_closure/later_consumer.dag" +data rdc_early_path: String = "dag/test/fixture/reference_derived_closure/early.dag" +data rdc_early_consumer_path: String = "dag/test/fixture/reference_derived_closure/early_consumer.dag" + +// Entry-invariance fixture. later.dag references LaterInLater ABOVE its declaration -- a module-sibling +// reference the source-order gate must refuse -- and also declares EarlyInLater above it, which the +// separate consumer projects into. The pair exists to be run twice with different entries: exposure is a +// property of the declaration, so which file the walk started from cannot change later.dag's verdict. +fn rdc_later_source() -> String { + "module test.fixture.reference_derived_closure.later\n" + + "\n" + + "type EarlyInLater\n" + + " = EarlyOne\n" + + " | EarlyTwo\n" + + "\n" + + "fn uses(y: LaterInLater) -> LaterInLater {\n" + + " y\n" + + "}\n" + + "\n" + + "type LaterInLater\n" + + " = LaterOne\n" + + " | LaterTwo\n" +} + +// The positive half of the same pair: early.dag's own reference sits BELOW its declaration, so it binds, +// and early_consumer projects into it. Both entries must settle -- otherwise the invariance row above is +// satisfiable by refusing everything. +fn rdc_early_source() -> String { + "module test.fixture.reference_derived_closure.early\n" + + "\n" + + "type EarlyOnly\n" + + " = OnlyOne\n" + + " | OnlyTwo\n" + + "\n" + + "fn uses(z: EarlyOnly) -> EarlyOnly {\n" + + " z\n" + + "}\n" +} + +fn rdc_early_consumer_source() -> String { + "module test.fixture.reference_derived_closure.early_consumer\n" + + "\n" + + "fn reads(e: early.EarlyOnly) -> early.EarlyOnly {\n" + + " e\n" + + "}\n" +} + +fn rdc_later_consumer_source() -> String { + "module test.fixture.reference_derived_closure.later_consumer\n" + + "\n" + + "fn reads(e: later.EarlyInLater) -> later.EarlyInLater {\n" + + " e\n" + + "}\n" +} + +fn rdc_consumer_source() -> String { + "module test.fixture.reference_derived_closure.consumer\n" + + "\n" + + "fn reads(d: live_tree.LiveTreeDisposition) -> live_tree.LiveTreeDisposition {\n" + + " d\n" + + "}\n" +} + +fn rdc_provider_source() -> String { + "module v2.std.live_tree\n" + + "\n" + + "type LiveTreeDisposition\n" + + " = ReadsLiveTree\n" + + " | HermeticOnly\n" +} + +fn rdc_second_provider_source() -> String { + "module test.fixture.reference_derived_closure.live_tree\n" + + "\n" + + "type LiveTreeDisposition\n" + + " = SecondOne\n" + + " | SecondTwo\n" +} + +fn rdc_unrelated_source() -> String { + "module test.fixture.reference_derived_closure.unrelated\n" + + "\n" + + "type UnrelatedSubject\n" + + " = OnlyOne\n" +} + +fn rdc_source_for(path: String) -> String? { + match path == rdc_consumer_path { + true => Present { value: rdc_consumer_source() } + false => + match path == rdc_provider_path { + true => Present { value: rdc_provider_source() } + false => + match path == rdc_second_provider_path { + true => Present { value: rdc_second_provider_source() } + false => + match path == rdc_unrelated_path { + true => Present { value: rdc_unrelated_source() } + false => + match path == rdc_later_path { + true => Present { value: rdc_later_source() } + false => + match path == rdc_later_consumer_path { + true => Present { value: rdc_later_consumer_source() } + false => + match path == rdc_early_path { + true => Present { value: rdc_early_source() } + false => + match path == rdc_early_consumer_path { + true => Present { value: rdc_early_consumer_source() } + false => none + } + } + } + } + } + } + } + } +} + +// Exposure is a fact about the declaration's own containment, not about the role its file plays in +// this closure: entry and provider files ground identically, so a file's accepted declarations cannot +// change with which file the walk started from. +fn rdc_grounding_for(path: String) -> DeclarationExposureGrounding { + ModuleLocalMemberExposure +} + +fn rdc_supply_for(path: String) -> ParsedFileSupply? { + match rdc_source_for(path: path) { + Absent => none + Present { value: source } => + match parse_authored_occurrence_binding_source(file: path, source: source) { + ParsedOccurrenceBindingSourceRefused => none + ParsedOccurrenceBindingSourceReady { transport: t, module_path: module_path } => + Present { + value: ParsedFileSupply { + file: path as FilePath, + row: cross_file_binding_closure_row( + transport: t, + inputs: occurrence_binding_inputs_from_transport( + transport: t, + module_path: module_path, + grounding: rdc_grounding_for(path: path), + ), + module_path: module_path, + file_path: path as FilePath, + ), + }, + } + } + } +} + +fn rdc_no_supply() -> List { [] } + +fn rdc_universe(paths: List) -> List { + fold(paths, init: rdc_no_supply(), f: (acc, path) => + match rdc_supply_for(path: path) { + Absent => acc + Present { value: row } => concat(acc, [row]) + }) +} + +fn rdc_step(entry: String, universe: List) -> ReferenceDerivedClosureStep { + reference_derived_closure_step( + state: reference_derived_closure_initial( + entry: entry as FilePath, + universe: rdc_universe(paths: universe), + ), + ) +} + +fn rdc_fixed_point(entry: String, universe: List) -> ReferenceDerivedClosureFixedPoint { + reference_derived_closure_fixed_point( + entry: entry as FilePath, + universe: rdc_universe(paths: universe), + ) +} + +fn rdc_ordinary_universe() -> List { + [rdc_consumer_path, rdc_provider_path] +} + +fn rdc_edges_name_provider(edges: List) -> Bool { + fold(edges, init: false, f: (found, e) => + found || ((e.provider_file as String) == rdc_provider_path)) +} + +fn rdc_files_contain(files: List, path: String) -> Bool { + fold(files, init: false, f: (found, f) => found || ((f as String) == path)) +} + +// THE KEYSTONE TRACE, and it is one step of a growing closure rather than a population handed to the step +// already complete. The consumer alone is admitted; the provider file is supplied only as an ordinary +// member of the source-file universe, with no provider-path input anywhere. Its declaration is therefore +// in the candidate index while its FILE is not in the closure, the bare reference binds to that exact +// declaration, and the edge that binding projects is what makes the provider pending. +test fn witness_consumer_alone_discovers_the_provider_file_as_pending() -> Bool { + match rdc_step(entry: rdc_consumer_path, universe: rdc_ordinary_universe()) { + ClosureStepAdvanced { + state: state, + newly_pending: newly_pending, + edges: edges, + provenance: provenance, + } => + length(newly_pending) == 1 + && rdc_files_contain(files: newly_pending, path: rdc_provider_path) + && rdc_files_contain(files: state.admitted_files, path: rdc_consumer_path) + && length(state.admitted_files) == 2 + && length(edges) == 1 + && rdc_edges_name_provider(edges: edges) + && length(provenance) >= 1 + _ => false + } +} + +// THE SECOND ITERATION SETTLES. Same universe, the provider now admitted by the first step's own returned +// state: pending is empty, the edge population is unchanged, and the closure is exactly the two files. +// Asserted from the step rather than only from the fixed point so the iteration and the driver are two +// separate claims. +test fn witness_second_step_over_the_advanced_state_settles() -> Bool { + match rdc_step(entry: rdc_consumer_path, universe: rdc_ordinary_universe()) { + ClosureStepAdvanced { state: advanced, newly_pending: _, edges: _, provenance: _ } => + match reference_derived_closure_step(state: advanced) { + ClosureStepSettled { state: state, edges: edges, provenance: _ } => + length(state.admitted_files) == 2 + && length(edges) == 1 + && rdc_edges_name_provider(edges: edges) + _ => false + } + _ => false + } +} + +// THE WHOLE FIXED POINT, driven to termination: two iterations, two files admitted, one edge. The +// iteration count is asserted because a driver that re-ran the same step forever would still report a +// settled closure, and a driver that stopped after one would report an Exhausted one. +test fn witness_fixed_point_settles_in_two_iterations_over_two_files() -> Bool { + match rdc_fixed_point(entry: rdc_consumer_path, universe: rdc_ordinary_universe()) { + ClosureFixedPointSettled { + admitted_files: admitted, + edges: edges, + provenance: _, + iterations: iterations, + } => + iterations == 2 + && length(admitted) == 2 + && rdc_files_contain(files: admitted, path: rdc_consumer_path) + && rdc_files_contain(files: admitted, path: rdc_provider_path) + && length(edges) == 1 + && rdc_edges_name_provider(edges: edges) + _ => false + } +} + +// MANY MENTIONS, ONE EDGE, NO SELF-EDGE. Every admitted mention keeps its binding RECEIPT -- including the +// same-file mention of the parameter `d`, which is clause-(e)-inducing and binds to its own declaration -- +// while the edge population deduplicates to one and no edge points a file at itself. The assertion is the +// carrier law rather than a count of the rows I expected to participate: I first asserted two provenance +// rows here and measurement said three. +test fn witness_mentions_keep_receipts_while_edges_dedupe_without_a_self_edge() -> Bool { + match rdc_fixed_point(entry: rdc_consumer_path, universe: rdc_ordinary_universe()) { + ClosureFixedPointSettled { + admitted_files: _, + edges: edges, + provenance: provenance, + iterations: _, + } => + length(provenance) == 3 + && length(edges) == 1 + && fold(edges, init: true, f: (ok, e) => + ok && !((e.consumer_file as String) == (e.provider_file as String))) + && fold(provenance, init: false, f: (found, row) => + found || ((row.consumer_file as String) == (row.provider_file as String))) + _ => false + } +} + +// THE UNIVERSE IS THE DENOMINATOR, so a provider absent from it refuses as UNBOUND naming the occurrence. +// This is the negative half of the keystone: it is the same consumer, the same entry and the same step, +// differing only in whether the declaring file is part of the compile at all. Neither wrong answer is +// available -- no one-file closure reported as settled, and no edge invented from a module manifest that +// cannot be asked which file declares a bare name. +test fn witness_provider_outside_the_universe_refuses_as_unbound() -> Bool { + match rdc_step(entry: rdc_consumer_path, universe: [rdc_consumer_path]) { + ClosureStepRefused { refusal: refusal } => + match refusal { + ClosureBindingRefused { + population: population, + first_failure_locus: locus, + failure_count: failure_count, + } => + match population { + ReferencePopulationRefused { first_failure: failure, more_failures: _ } => + (locus.authored_name == "live_tree.LiveTreeDisposition") + && locus.diagnostic_span.start > 0 + && failure_count >= 1 + && match failure { + ReferenceBindingProjectionUnbound { occurrence: _ } => true + _ => false + } + _ => false + } + _ => false + } + _ => false + } +} + +// AN ENTRY THAT IS NOT IN THE UNIVERSE refuses rather than being parsed through a second admission path. +// Without this row the step could quietly treat a missing entry as an empty closure, which every consumer +// downstream would read as "this file depends on nothing". +test fn witness_entry_absent_from_the_universe_refuses() -> Bool { + match rdc_step(entry: rdc_consumer_path, universe: [rdc_provider_path]) { + ClosureStepRefused { refusal: refusal } => + match refusal { + ClosureNoEntryAdmitted => true + _ => false + } + _ => false + } +} + +// TWO ADMISSIBLE DECLARATIONS of one name is ambiguous, and the refusal carries BOTH competing identities, +// distinct from each other -- the answer is which two, not how many. Note it fires on the FIRST step: the +// ambiguity is in the universe-wide index, so it is caught before any file is admitted on its account. +test fn witness_two_declaring_files_refuse_as_ambiguous_naming_both() -> Bool { + match rdc_step( + entry: rdc_consumer_path, + universe: [rdc_consumer_path, rdc_provider_path, rdc_second_provider_path], + ) { + ClosureStepRefused { refusal: refusal } => + match refusal { + ClosureBindingRefused { + population: population, + first_failure_locus: locus, + failure_count: failure_count, + } => + match population { + ReferencePopulationRefused { first_failure: failure, more_failures: _ } => + match failure { + ReferenceBindingProjectionAmbiguous { occurrence: _, candidates: candidates } => + (locus.authored_name == "live_tree.LiveTreeDisposition") + && failure_count >= 1 + && !(candidates.first.containment.terminal.value == + candidates.second.containment.terminal.value) + _ => false + } + _ => false + } + _ => false + } + _ => false + } +} + +// A UNIVERSE FILE NOTHING REFERENCES IS NOT ADMITTED. The closure is per-entry, so breadth of the universe +// must not become breadth of the closure -- the failure this excludes is a loader that admits the compile's +// whole source population and reports it as the entry's dependencies. +test fn witness_unreferenced_universe_file_is_not_admitted() -> Bool { + match rdc_fixed_point( + entry: rdc_consumer_path, + universe: [rdc_consumer_path, rdc_provider_path, rdc_unrelated_path], + ) { + ClosureFixedPointSettled { + admitted_files: admitted, + edges: edges, + provenance: _, + iterations: _, + } => + length(admitted) == 2 + && !rdc_files_contain(files: admitted, path: rdc_unrelated_path) + && length(edges) == 1 + _ => false + } +} + +// The exposure model's load-bearing property, and the control that caught its absence. Before +// 2026-08-13 a provider file's module members were ground to RootExposure, so later.dag's premature +// LaterInLater reference bound when later.dag was reached as a provider and refused when it was the +// entry: the accepted declarations for one file were a function of which file the walk started from, +// which is not a property a language may have. Both runs must now refuse at the SAME authored name and +// the SAME span. OccurrenceId is deliberately not compared across the two runs -- each assembly rekeys +// into its own allocator scope, so equal ids would prove nothing and unequal ids would refuse a correct +// closure. +fn rdc_binding_refusal_locus( + outcome: ReferenceDerivedClosureFixedPoint +) -> OccurrenceProjection? { + match outcome { + ClosureFixedPointRefused { refusal: refusal, iterations: _ } => + match refusal { + ClosureBindingRefused { + population: _, + first_failure_locus: locus, + failure_count: _, + } => Present { value: locus } + _ => none + } + _ => none + } +} + +test fn witness_later_declaration_refuses_identically_under_both_entries() -> Bool { + let universe = [rdc_later_consumer_path, rdc_later_path] + match rdc_binding_refusal_locus( + outcome: rdc_fixed_point(entry: rdc_later_path, universe: universe), + ) { + Absent => false + Present { value: as_entry } => + match rdc_binding_refusal_locus( + outcome: rdc_fixed_point(entry: rdc_later_consumer_path, universe: universe), + ) { + Absent => false + Present { value: as_provider } => + (as_entry.authored_name == "LaterInLater") + && (as_provider.authored_name == as_entry.authored_name) + && (as_provider.diagnostic_span.start == as_entry.diagnostic_span.start) + && (as_provider.diagnostic_span.end == as_entry.diagnostic_span.end) + } + } +} + +// The other half of invariance: a declaration that IS in scope binds under both entries. Without this +// the row above is satisfiable by refusing everything. +test fn witness_earlier_declaration_binds_under_both_entries() -> Bool { + let universe = [rdc_early_consumer_path, rdc_early_path] + match rdc_fixed_point(entry: rdc_early_path, universe: universe) { + ClosureFixedPointSettled { + admitted_files: alone, + edges: _, + provenance: _, + iterations: _, + } => + match rdc_fixed_point(entry: rdc_early_consumer_path, universe: universe) { + ClosureFixedPointSettled { + admitted_files: reached, + edges: _, + provenance: _, + iterations: _, + } => + rdc_files_contain(files: alone, path: rdc_early_path) + && !rdc_files_contain(files: alone, path: rdc_early_consumer_path) + && rdc_files_contain(files: reached, path: rdc_early_path) + && rdc_files_contain(files: reached, path: rdc_early_consumer_path) + _ => false + } + _ => false + } +} diff --git a/src/v2/compiler/source_authority.dag b/src/v2/compiler/source_authority.dag index 85c441d56c4..0c970ed100f 100644 --- a/src/v2/compiler/source_authority.dag +++ b/src/v2/compiler/source_authority.dag @@ -23,6 +23,7 @@ import v2.extdeps.languages.dag { import v2.std.compilers.lexing { TokenStream } import v2.std.provenance { span_index_empty } import std.algebra { Empty, FreeMonoid } +import std.occurrence_binding_candidates { ModulePathFileRow } import v2.std.algebra { fold_list, length, list_snoc_item } import v2.std.artifact { Artifact, NodeArtifactProvenance, SourceFile } import v2.std.diagnostic { @@ -694,6 +695,116 @@ type ModuleStorageBinding { type ModuleStorageIndex = FreeMonoid +// THE SCAFFOLD PROJECTION, FINALLY PROJECTING. module_path_file_row_note declares this authority -- +// ModuleStorageBinding / ModuleStorageIndex -- as the single home for the path-to-module binding, and +// calls ModulePathFileRow a scaffold projection OF it. Until now nothing performed that projection: +// every ModulePathFileRow in the tree was a hand-authored literal in a witness, which is the +// parallel-representation state the note said it was avoiding. This is the missing function, and it +// lives HERE rather than in std because the binding is this module's fact. +// +// TWO ARMS, because a module does not always have a file. ParsedFromSource carries an Artifact whose +// file_path IS the storage realization, so the row is derivable. ProducedByBehavior means a module the +// compiler produced -- there is no file, and there is no honest path to invent for it. Fabricating one +// (the module name reshaped into a path, an empty string, the producer's own file) is exactly the +// fabricated plausible output DESIGN section 5 forbids, and it would be undetectable downstream +// because a path-shaped string is indistinguishable from a real path. So the projection REFUSES and +// names the module and its producer. +// +// WHY THE INDEX PROJECTION REFUSES WHOLE. One unprojectable binding means the derived row set is not +// the index, and a consumer that received the survivors would believe it held the whole storage +// picture -- the empty-observation narrow at set grain. Callers that legitimately want only the +// file-backed subset must say so by filtering the index before projecting, which is a statement about +// intent rather than a silent loss. + +type ModulePathFileRowProjection + = ModulePathFileRowProjected { row: ModulePathFileRow } + | ModulePathFileRowNoStorageRealization { + module: QualifiedName, + producer: Symbol, + } + +fn module_path_file_row_from_storage_binding( + binding: ModuleStorageBinding +) -> ModulePathFileRowProjection { + match binding.provenance { + ParsedFromSource { artifact: artifact, span_index: _ } => + ModulePathFileRowProjected { + row: ModulePathFileRow { + module_path: qualified_name_to_dotted_string(qn: binding.module) as NonEmptyStr, + file_path: artifact.file_path as FilePath, + }, + } + ProducedByBehavior { producer: producer } => + ModulePathFileRowNoStorageRealization { + module: binding.module, + producer: producer, + } + } +} + +type ModulePathFileRowsFromIndex + = ModulePathFileRowsFromIndexReady { rows: List } + | ModulePathFileRowsFromIndexRefused { + first_failure: ModulePathFileRowProjection, + more_failures: List, + } + +type ModulePathFileRowsBuild { + rows_reversed: List + first_failure: ModulePathFileRowProjection? + more_failures_reversed: List +} + +fn module_path_file_rows_from_storage_index( + index: ModuleStorageIndex +) -> ModulePathFileRowsFromIndex { + let build = fold_list( + index, + ModulePathFileRowsBuild { + rows_reversed: [], + first_failure: none, + more_failures_reversed: [], + }, + (acc, binding) => + match module_path_file_row_from_storage_binding(binding: binding) { + ModulePathFileRowProjected { row: row } => + ModulePathFileRowsBuild { + rows_reversed: concat([row], acc.rows_reversed), + first_failure: acc.first_failure, + more_failures_reversed: acc.more_failures_reversed, + } + ModulePathFileRowNoStorageRealization { module: m, producer: p } => + match acc.first_failure { + Absent => + ModulePathFileRowsBuild { + rows_reversed: acc.rows_reversed, + first_failure: Present { + value: ModulePathFileRowNoStorageRealization { module: m, producer: p }, + }, + more_failures_reversed: acc.more_failures_reversed, + } + Present { value: _ } => + ModulePathFileRowsBuild { + rows_reversed: acc.rows_reversed, + first_failure: acc.first_failure, + more_failures_reversed: concat( + [ModulePathFileRowNoStorageRealization { module: m, producer: p }], + acc.more_failures_reversed, + ), + } + } + }, + ) + match build.first_failure { + Absent => ModulePathFileRowsFromIndexReady { rows: reverse(build.rows_reversed) } + Present { value: first_failure } => + ModulePathFileRowsFromIndexRefused { + first_failure: first_failure, + more_failures: reverse(build.more_failures_reversed), + } + } +} + type ModuleStorageFoldState = ModuleStorageFoldOk { bindings: ModuleStorageIndex diff --git a/src/v2/test/claim/ci_floor_plan_witness_test.dag b/src/v2/test/claim/ci_floor_plan_witness_test.dag index 9fc028c55ac..ca1ffcb624a 100644 --- a/src/v2/test/claim/ci_floor_plan_witness_test.dag +++ b/src/v2/test/claim/ci_floor_plan_witness_test.dag @@ -380,7 +380,7 @@ fn v1_claim_scoped_known_red_probe_batch_holds(r: Runnable) -> Bool { (roots == ["dag", "src/v1"]) && (length(xs: dirs) == 0) && (length(xs: entries) == length(xs: known_red_probe_v1_envelope_roster())) - && (mode == SelectionApplied) + && (mode == SelectionOff) && discovery_batch_declares_hermetic(r: r) && (p.spawns_host_compiler == false) RunnableScopedWitnessBatch { batch: _ } => false @@ -949,8 +949,15 @@ test fn witness_live_floor_discovery_batch_count_includes_wet_lanes() -> Bool { } } -test fn witness_live_floor_discovery_batch_count_is_four() -> Bool { - schedule_discovery_batch_count(batches: gunbc_ci_floor_ordinary_batches()) == 4 +// FOUR is the UNCONDITIONAL discovery population; the known-red v1-envelope probe is a fifth +// RunnableDiscoveryBatch that exists only while its roster is non-empty, so it enters as a named +// conditional term. Folding it into the constant would make an enrollment invisible here, and +// leaving it out entirely reds a claim about the unconditional lanes for a reason belonging to the +// probe — the same coupling gunbc.ci_spec gunbc_ci_v1_envelope_known_red_probe_clamp_basis_note +// carries on the clamp side. +test fn witness_live_floor_discovery_batch_count_is_four_plus_enrolled_probes() -> Bool { + let probe = if v1_claim_scoped_known_red_probe_batch_enrolled() { 1 } else { 0 } + schedule_discovery_batch_count(batches: gunbc_ci_floor_ordinary_batches()) == 4 + probe } test fn ci_floor_plan_witnesses() -> Bool { @@ -964,7 +971,7 @@ test fn ci_floor_plan_witnesses() -> Bool { witness_floor_schedule_preserves_enrolled_set() && witness_live_floor_schedules_bin_witness_wet_batch() && witness_live_floor_discovery_batch_count_includes_wet_lanes() && - witness_live_floor_discovery_batch_count_is_four() && + witness_live_floor_discovery_batch_count_is_four_plus_enrolled_probes() && witness_non_cheap_gate_absent_from_early_batch() && witness_floor_stop_policy_pr_is_fail_fast() && witness_floor_stop_policy_push_is_full_ledger() && diff --git a/src/v2/test/claim/module_storage_row_supply_test.dag b/src/v2/test/claim/module_storage_row_supply_test.dag new file mode 100644 index 00000000000..21316598a77 --- /dev/null +++ b/src/v2/test/claim/module_storage_row_supply_test.dag @@ -0,0 +1,163 @@ +module v2.test.claim.module_storage_row_supply + +import std.occurrence_binding_candidates { ModulePathFileRow } +import v2.compiler.source_authority { + ModulePathFileRowsFromIndex, + ModulePathFileRowsFromIndexReady, + ModulePathFileRowsFromIndexRefused, + ModulePathFileRowProjection, + ModulePathFileRowProjected, + ModulePathFileRowNoStorageRealization, + module_path_file_rows_from_storage_index, +} +import v2.test.workflow.host_module_binding_manifest { + host_module_binding_count, + host_module_bindings, +} +import v2.std.algebra { length } +import v2.std.collection { Absent, Present } +import v2.std.live_tree { LiveTreeDisposition, ReadsLiveTree } +import v2.std.logic { Bool } +import v2.std.text { String } + +data live_tree_disposition: LiveTreeDisposition = ReadsLiveTree + +data msrs_note: String = "PROVIDER ROWS FROM THE REAL TREE, not from a hand-selected path. This is the second half of the import-deletion lane's provider discovery, and the point of it is what it does NOT add: no filesystem walker, no name scanner, no module-to-file map and no second dependency producer. The whole-tree PARSED module-to-path rows already exist -- discover_source_root_ingest --emit-module-binding-manifest emits them via v1_compiler_parse, carrying no source text and therefore uncapped -- and module_binding_supply_transport already overlays them as a MODULE in an ephemeral source root. So the rows arrive as data through an existing carrier, and this witness projects them through the authority. + +Arriving as a module rather than through a filesystem read is what makes this reachable at all. A witness that read the tree with filesystem_read would drag the Filesystem service into its emission closure; under src/v1 that produces an unregisterable dry_run.rs, which is the placement contradiction the sibling import_deletion_bridge witness records. Overlaid data has no such cost." + +data msrs_vacuity_note: String = "THE TWO VACUOUS GREENS, both closed, because this witness is only meaningful when the overlay is present. Run without the transport, host_module_bindings is the committed Empty stub and host_module_binding_count is 0 -- and an empty index projects to an empty row set that is legitimately Ready. Every claim below would then pass while observing nothing. So each row requires the manifest to be POPULATED and the projected set to be NON-EMPTY before it asserts anything, exactly as the sibling consistency witness requires a non-empty oracle. Neither arm widens: both refuse." + +fn msrs_projected_rows() -> List? { + match host_module_binding_count > 0 { + false => none + true => + match module_path_file_rows_from_storage_index(index: host_module_bindings) { + ModulePathFileRowsFromIndexRefused { first_failure: _, more_failures: _ } => none + ModulePathFileRowsFromIndexReady { rows: rows } => + match length(rows) > 0 { + true => Present { value: rows } + false => none + } + } + } +} + +// THE POSITIVE CONTROL, and the vacuity guard for every row below it. Without this, an absent overlay +// would make the others pass by observing nothing. +test fn msrs_real_tree_rows_project() -> Bool { + match msrs_projected_rows() { + Absent => false + Present { value: rows } => length(rows) > 0 + } +} + +// EVERY row the manifest carries projects. Stated as a count identity rather than "some rows +// appeared": the projection refuses the whole index on any binding it cannot realize, so a Ready +// answer over the real tree is the claim that the entire live corpus is file-backed -- and if it ever +// is not, this row is where that shows up, as a refusal rather than as a shorter list. +test fn msrs_every_manifest_binding_projects_to_a_row() -> Bool { + match msrs_projected_rows() { + Absent => false + Present { value: rows } => length(rows) == host_module_binding_count + } +} + +// A KNOWN REAL MODULE resolves to its KNOWN REAL PATH. This is the row that makes the population an +// observation of this repository rather than a shape check: the pair is authored here and must be +// found in rows derived from the live tree, so a projection that produced well-formed rows about the +// wrong files fails it. The subject is deliberately the same module the import-deletion bridge +// witness supplies by hand -- that hand-selected path is what this replaces. +test fn msrs_a_known_module_resolves_to_its_real_file() -> Bool { + msrs_pair_present( + module_path: "v2.std.live_tree", + file_path: "src/v2/std/live_tree.dag", + ) +} + +// THE dag/ ROOT IS COVERED, and these are the three rows whose absence the removed comment recorded. +// +// The producer minted the artifact identity as a `^stem` symbol literal from the file BASENAME, so the +// three dag/ files whose stems are reserved .dag keywords emitted `^capability`, `^module` and `^import` +// and the manifest did not parse -- half the corpus undescribable, and a reference-derived closure over +// the real tree cannot be completed while providers in dag/ cannot be named. The repair took the basename +// out of the identity decision rather than escaping it: v1_compiler.cli_run +// source_root_ingest_artifact_id_for_path interns the repo-relative PATH the row already carries, through +// the string-taking Symbol authority, so keywords are inert -- and two same-named files in different +// directories stop colliding on one identity, which the stem form did silently. +// +// One row per keyword, not one row for the class: each is a distinct file whose stem is a distinct +// keyword, and a repair that fixed only the one a class-level assertion happened to visit would pass it. +test fn msrs_keyword_stem_import_module_resolves_to_its_real_file() -> Bool { + msrs_pair_present(module_path: "std.import", file_path: "dag/std/import.dag") +} + +test fn msrs_keyword_stem_go_module_resolves_to_its_real_file() -> Bool { + msrs_pair_present( + module_path: "extdeps.languages.go.module", + file_path: "dag/extdeps/languages/go/module.dag", + ) +} + +test fn msrs_keyword_stem_capability_resolves_to_its_real_file() -> Bool { + msrs_pair_present( + module_path: "extdeps.bmc.capability", + file_path: "dag/extdeps/bmc/capability.dag", + ) +} + +// TWO ROOTS IN ONE INDEX. The rows above and the src/v2 row are separately asserted pairs; this asks the +// population question they cannot: that the index spans BOTH ordinary source roots at once. A producer +// invoked with one root would satisfy every pair it happened to cover and fail this. +test fn msrs_index_spans_both_ordinary_source_roots() -> Bool { + match msrs_projected_rows() { + Absent => false + Present { value: rows } => + msrs_root_covered(rows: rows, prefix: "dag/") + && msrs_root_covered(rows: rows, prefix: "src/v2/") + } +} + +fn msrs_root_covered(rows: List, prefix: String) -> Bool { + fold(rows, init: false, f: (found, row) => + found || ((row.file_path as String).starts_with(prefix: prefix))) +} + +// THE NEGATIVE CONTROL. A module that does not exist must not appear, and the pair check must be +// capable of answering no -- otherwise the positive rows prove only that the fold visited something. +test fn msrs_an_absent_module_is_not_projected() -> Bool { + match msrs_projected_rows() { + Absent => false + Present { value: _ } => + !msrs_pair_present( + module_path: "v2.std.module_that_does_not_exist", + file_path: "src/v2/std/module_that_does_not_exist.dag", + ) + } +} + +// A REAL MODULE PAIRED WITH THE WRONG FILE must not be present either. Distinct from the row above: +// that one asks whether an unknown NAME leaks in, this one asks whether a known name is bound to a +// path it does not have -- the failure a projection that reshaped module names into paths would +// produce, and it would pass the absent-module row. +test fn msrs_a_real_module_is_not_bound_to_the_wrong_file() -> Bool { + match msrs_projected_rows() { + Absent => false + Present { value: _ } => + !msrs_pair_present( + module_path: "v2.std.live_tree", + file_path: "src/v2/std/live_tree_elsewhere.dag", + ) + } +} + +fn msrs_pair_present(module_path: String, file_path: String) -> Bool { + match msrs_projected_rows() { + Absent => false + Present { value: rows } => + fold(rows, init: false, f: (found, row) => + found + || ((row.module_path as String) == module_path + && (row.file_path as String) == file_path)) + } +} diff --git a/src/v2/test/claim/pr_native_batch_test.dag b/src/v2/test/claim/pr_native_batch_test.dag index 7c7012234e0..62f4bf9c39e 100644 --- a/src/v2/test/claim/pr_native_batch_test.dag +++ b/src/v2/test/claim/pr_native_batch_test.dag @@ -4,7 +4,8 @@ import v2.workflow.ci_floor_plan { gunbc_pr_native_entries, gunbc_pr_native_batch_enrolled, gunbc_ci_floor_ordinary_batches, - gunbc_ci_floor_realization_plan_for + gunbc_ci_floor_realization_plan_for, + v1_claim_scoped_known_red_probe_batch_enrolled } import gunbc.ci_layer_roots { scoped_witness_batches } import gunbc.ci_spec { gunbc_ci_spec } @@ -28,8 +29,17 @@ test fn witness_selected_native_bundle_enrolled() -> Bool { && gunbc_pr_native_entries().first().kind == NativeBundleWitnessKind } +// The subject is the NATIVE append and nothing else, so every other conditionally-appended batch +// enters as its own named term rather than being folded into the constant. The known-red v1-envelope +// probe (gunbc.explicit_witness_admission known_red_probe_v1_envelope_roster) appends one batch when +// its roster is non-empty; without the term, enrolling a probe reds a witness about the native lane +// and reads as a native-append defect. +fn known_red_probe_appended_batch_count() -> Int { + if v1_claim_scoped_known_red_probe_batch_enrolled() { 1 } else { 0 } +} + test fn witness_floor_appends_one_selected_native_batch() -> Bool { - length(xs: gunbc_ci_floor_ordinary_batches()) == length(xs: gunbc_ci_floor_realization_plan_for(spec: gunbc_ci_spec).schedule) + length(xs: scoped_witness_batches) + 1 + length(xs: gunbc_ci_floor_ordinary_batches()) == length(xs: gunbc_ci_floor_realization_plan_for(spec: gunbc_ci_spec).schedule) + length(xs: scoped_witness_batches) + 1 + known_red_probe_appended_batch_count() } fn native_transition_receipt_fixture(selected: Int, native: Int, interpreted: Int, unavailable: Int, diverged: Int, fallback: Int, verdict: NativeProductionTransitionVerdict) -> NativeWitnessProductionTransitionReceipt { diff --git a/src/v2/workflow/ci_floor_plan.dag b/src/v2/workflow/ci_floor_plan.dag index 78d88ed9fb1..9dff2ad2755 100644 --- a/src/v2/workflow/ci_floor_plan.dag +++ b/src/v2/workflow/ci_floor_plan.dag @@ -426,6 +426,13 @@ fn gunbc_ci_floor_plan_uses_batch_stop_policy(plan_function: String) -> Bool { || (plan_function == falsifier_plan_function) } +// THE DECLARED SIDE of witness_floor_schedule_preserves_enrolled_set, and it must carry a term for +// every conditionally-appended batch the schedule side can produce. Both conditional batches — the +// PR native bundle and the known-red v1-envelope probe — are appended to the schedule by +// gunbc_ci_floor_ordinary_batches under their own enrollment predicates, so a batch enrolled here +// and absent there (or the reverse) is precisely the set difference this pair exists to catch. The +// probe term was owed when the probe batch was introduced; without it, enrolling one known-red row +// reds a witness about schedule/enrollment agreement for a reason that is agreement working. fn gunbc_ci_floor_enrolled_runnable_labels_for(spec: CiSpec) -> List { let gate_labels = list_map(xs: spec.gates, f: fn(g) { runnable_single_claim_function(r: gate_runnable(g: g)) @@ -434,11 +441,16 @@ fn gunbc_ci_floor_enrolled_runnable_labels_for(spec: CiSpec) -> List { runnable_single_claim_function(r: runnable_for_node(node: node, spec: spec)) }) let spec_labels = concat(concat(gate_labels, corpus_labels), map(scoped_witness_batches, batch => "__scoped_witness_batch__")) - if gunbc_pr_native_batch_enrolled() { + let with_native = if gunbc_pr_native_batch_enrolled() { list_snoc_item(xs: spec_labels, item: runnable_single_claim_function(r: gunbc_pr_native_batch())) } else { spec_labels } + if v1_claim_scoped_known_red_probe_batch_enrolled() { + list_snoc_item(xs: with_native, item: runnable_single_claim_function(r: gunbc_v1_claim_scoped_known_red_probe_batch())) + } else { + with_native + } } fn gunbc_ci_floor_schedule_runnable_labels(batches: List>) -> List { @@ -1362,6 +1374,8 @@ fn gunbc_ci_floor_ordinary_batches() -> List> { ) } +data v1_claim_scoped_known_red_probe_selection_note: String = "SELECTION IS OFF HERE, and the reason is a property of this batch's envelope rather than a preference. Affected-set selection resolves its own machinery -- v2.workflow.affected_set_floor_runner among others -- from the batch's source roots, and this batch's roots are exactly dag plus src/v1 because that is the envelope its subject needs. Under SelectionApplied the run therefore refused fail-closed with the runner having no provenance in the module-graph pool, which is the selection machinery correctly reporting that it is outside every root rather than a defect in the roster. The sibling v1_claim_scoped_witness_batch avoids this only because it is a RunnableScopedWitnessBatch carrying InheritedWalkSourceRoots, so its worker resolves that machinery from the ENCLOSING walk; a plain discovery batch has no such authority. Widening the roots to src/v2 was rejected for the reason that batch's own note gives -- it moves the subject envelope and invalidates the measured clamp basis. Selection buys nothing here regardless: the roster is a handful of explicit expect-red entries, so there is no unrelated population to skip." + data v1_claim_scoped_known_red_probe_batch_note: String = "Per-PR Hermetic expect_red lane for known-red CorpusWitnessKind rows whose subject needs the dag plus src/v1 envelope (gunbc.explicit_witness_admission known_red_probe_v1_envelope_roster: src/v1 paths plus dag witnesses that import v1.*). The falsifier's corpus-root probe batch cannot resolve these entries; running them there would treat resolve-refuse as expect-red success without executing the predicate (DESIGN section 5 specification-without-execution). This batch mirrors v1_claim_scoped_witness_batch's dag plus src/v1 source-root envelope and runs SelectionApplied on the ordinary floor. ENROLLMENT COUPLING: enrolling this batch (known_red_probe_v1_envelope_roster becomes non-empty) requires adding its companion positional row to gunbc.ci_spec gunbc_ci_floor_batch_clamp_params in the same change — the clamp table is unconditional data while this batch is conditionally enrolled, so a roster-only edit that enrolls the batch without the clamp row fails with floor batch clamp params must cover N scheduled batches." fn v1_claim_scoped_known_red_probe_batch_enrolled() -> Bool { @@ -1373,7 +1387,7 @@ fn gunbc_v1_claim_scoped_known_red_probe_batch() -> Runnable { source_roots: ["dag", "src/v1"], scan_dirs: [], explicit_entries: known_red_probe_v1_envelope_roster(), - node_frontier_selection: SelectionApplied, + node_frontier_selection: SelectionOff, exclude_substrings: [], discovery_scope_dirs: [], profile: runnable_resource_profile( diff --git a/src/v2/workflow/module_binding_supply_transport.dag b/src/v2/workflow/module_binding_supply_transport.dag index 8f5bfba2c26..16f651f4b93 100644 --- a/src/v2/workflow/module_binding_supply_transport.dag +++ b/src/v2/workflow/module_binding_supply_transport.dag @@ -24,12 +24,20 @@ data mbs_overlay_ephemeral_note: String = "The overlay directory MUST be ephemer Per-run mktemp plus unconditional cleanup makes the collision UNWRITABLE rather than order-dependent (DESIGN section 5 — construction over validation): there is no fixed path left for a second run to find, so gate ORDER stops being load-bearing for correctness. This mirrors compiler_closure_ingest_transport, which had the same overlay-under-a-scanned-root hazard and already solved it this way; the fix is adopting the sibling's shape, not inventing one." data mbs_source_root: String = "src/v2" + +data mbs_dag_source_root: String = "dag" data mbs_oracle_scan_dir: String = "src/v2/test/fixture/program_assembly" data mbs_overlay_template: String = "/target/module-binding-supply-XXXXXX" data mbs_entry: String = "src/v2/test/claim/long/module_binding_host_consistency_test.dag" data mbs_layout_entry: String = "src/v2/test/claim/self_host/crate_layout_witness_test.dag" +data mbs_row_supply_entry: String = "src/v2/test/claim/module_storage_row_supply_test.dag" + +data mbs_row_supply_note: String = "The ModulePathFileRow projection runs HERE for the same reason the crate-layout witnesses do: it consumes the derived binding index, and the real index exists only inside this overlay. Unlike the oracle claims it does NOT re-parse anything -- the manifest is already data and the projection is a fold over it -- so it is an ordinary claim rather than a long-lane one. It is the import-deletion lane's provider-row supply: the rows it projects are what replaces that lane's hand-selected provider path. + +SCOPE: these rows observe BOTH ordinary source roots, dag/ and src/v2. The dag/ half was previously unreachable and the reason was in the producer, not in the scope: the artifact identity was minted as a `^stem` symbol literal from the file BASENAME, so dag/std/import.dag, dag/extdeps/languages/go/module.dag and dag/extdeps/bmc/capability.dag emitted `^import`, `^module` and `^capability` -- reserved keywords -- and the manifest did not parse. The basename is no longer in the identity decision at all: v1_compiler.cli_run source_root_ingest_artifact_id_for_path interns the repo-relative PATH the row already carries, through the string-taking Symbol authority, so keywords are inert and two same-named files in different directories no longer collide on one identity. Measured after the repair: 3533 bindings over dag + src/v2, manifest parsed and resolved, all five projections green." + data mbs_layout_claims_note: String = "The crate-layout witnesses run HERE, in the same overlay, because task 3 made them projections off the derived binding index instead of readers of a 27-row hand table. They need the real index, and the real index exists only inside this overlay — so this transport is their executing consumer. They were removed from compiler_frontier_census_holds in the same change (frontier_census_layout_relocation_note): the census is a pure zero-arity fast-lane gate that cannot obtain a host index, and the layer DAG forbids a compiler module importing v2.test.* to reach one. Enrolling them here is the other half of that removal, not a follow-on — a removal without this enrollment would leave a window where nothing checks crate layout at all. @@ -43,12 +51,31 @@ fn mbs_claim_source_roots(overlay_dir: String) -> List { fn mbs_emit_args(overlay_dir: String) -> List { [ "--source-root", mbs_source_root, + "--source-root", mbs_dag_source_root, "--scan-dir", mbs_oracle_scan_dir, "--emit-dag-manifest", concat(overlay_dir, "/host_source_root_ingest_manifest.dag"), "--emit-module-binding-manifest", concat(overlay_dir, "/host_module_binding_manifest.dag") ] } +data module_binding_supply_claims: List = [ + ClaimRun { entry: mbs_entry, function: "module_binding_host_manifest_is_populated" }, + ClaimRun { entry: mbs_entry, function: "module_binding_host_resolves_a_known_compiler_module" }, + ClaimRun { entry: mbs_entry, function: "module_binding_host_agrees_with_parse_oracle" }, + ClaimRun { entry: mbs_layout_entry, function: "projection_refuses_on_absent_index" }, + ClaimRun { entry: mbs_layout_entry, function: "transition_parity_host_index_agrees_with_hand_rows" }, + ClaimRun { entry: mbs_layout_entry, function: "crate_layout_witnesses" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_real_tree_rows_project" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_every_manifest_binding_projects_to_a_row" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_a_known_module_resolves_to_its_real_file" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_keyword_stem_import_module_resolves_to_its_real_file" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_keyword_stem_go_module_resolves_to_its_real_file" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_keyword_stem_capability_resolves_to_its_real_file" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_index_spans_both_ordinary_source_roots" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_an_absent_module_is_not_projected" }, + ClaimRun { entry: mbs_row_supply_entry, function: "msrs_a_real_module_is_not_bound_to_the_wrong_file" } +] + func run_module_binding_supply_gate_ready(root: String) -> Bool { let target_ready = shell.Mkdir.Parents(path: concat(root, "/target")) if target_ready.success { @@ -64,14 +91,7 @@ func run_module_binding_supply_gate_ready(root: String) -> Bool { let ran = if emitted.success { run_gunbc_claims( source_roots: mbs_claim_source_roots(overlay_dir: overlay.path), - claims: [ - ClaimRun { entry: mbs_entry, function: "module_binding_host_manifest_is_populated" }, - ClaimRun { entry: mbs_entry, function: "module_binding_host_resolves_a_known_compiler_module" }, - ClaimRun { entry: mbs_entry, function: "module_binding_host_agrees_with_parse_oracle" }, - ClaimRun { entry: mbs_layout_entry, function: "projection_refuses_on_absent_index" }, - ClaimRun { entry: mbs_layout_entry, function: "transition_parity_host_index_agrees_with_hand_rows" }, - ClaimRun { entry: mbs_layout_entry, function: "crate_layout_witnesses" } - ] + claims: module_binding_supply_claims ) } else { false